home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 6 code / TCP / NewsWatcher / NW Source / Source / subject.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-06-16  |  90.9 KB  |  3,455 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     subject.c
  4.  
  5.     This module handles subject windows.
  6.     
  7.     Copyright © 1994-1995, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <ctype.h>
  14. #include <stdlib.h>
  15.  
  16. #include "glob.h"
  17. #include "subject.h"
  18. #include "child.h"
  19. #include "mark.h"
  20. #include "news.h"
  21. #include "newswatcher.h"
  22. #include "dialog.h"
  23. #include "status.h"
  24. #include "menus.h"
  25. #include "next.h"
  26. #include "ldef.h"
  27. #include "article.h"
  28. #include "collapse.h"
  29. #include "drawutil.h"
  30. #include "memutil.h"
  31. #include "listutil.h"
  32. #include "windutil.h"
  33. #include "thread.h"
  34. #include "tescroll.h"
  35. #include "cancel.h"
  36. #include "header.h"
  37. #include "wind.h"
  38. #include "strutil.h"
  39. #include "message.h"
  40. #include "fileutil.h"
  41. #include "group.h"
  42. #include "olddrag.h"
  43. #include "dragutil.h"
  44. #include "print.h"
  45. #include "key.h"
  46. #include "teutil.h"
  47. #include "search.h"
  48. #include "ic.h"
  49. #include "help.h"
  50.  
  51.  
  52.  
  53. #define kMinWindowWidth        320                /* minimum window width */
  54.  
  55. #define kExtractBinariesManuallyDlg            149
  56. #define kExtractBinariesManuallyLabelItem    3
  57. #define kExtractBinariesManuallyListItem    4
  58.  
  59.  
  60.  
  61. static ListHandle gExtractBinariesManuallyList;
  62.  
  63. static Boolean gFirstListClickCall;        /* true if first call to ClickLoop function */
  64. static OSErr gClickLoopErr;                /* click loop error code */
  65. static WindowPtr gDragSrcWindow;        /* pointer to drag source window  */
  66. static FSSpec gDragTheFile;                /* file spec for saved dragged subjects */
  67. static Boolean gDraggedToFinder;        /* true if subjects dragged to Finder */
  68. static short gDragDestRow;                /* destination row when dragging subjects
  69.                                            in extract binaries manually dialog */
  70.  
  71. static ListClickLoopUPP gExtractBinariesManuallyClickLoopUPP;
  72. static ListClickLoopUPP gOldListClickLoopUPP;
  73. static ListClickLoopUPP gSubjectListClickLoopUPP;
  74. static DragTrackingHandlerUPP gExtractBinariesManuallyHandleTrackingUPP;
  75. static DragReceiveHandlerUPP gExtractBinariesManuallyHandleReceiveUPP;
  76. static DragSendDataUPP gDragSubjectsSendProcUPP;
  77. static ModalFilterUPP gExtractBinariesManuallyDialogFilterUPP;
  78. static UserItemUPP gExtractBinariesManuallyDlgLabelUserItemUPP;
  79. static UserItemUPP gExtractBinariesManuallyDlgListUserItemUPP;
  80.  
  81.  
  82.  
  83. /*----------------------------------------------------------------------------
  84.     GetSubjects
  85.     
  86.     Get a range of subject headers from the news server and store them in
  87.     a subject array.
  88.     
  89.     Entry:    newsGroup = name of the group.
  90.             first = first article number to get.
  91.             last = last article number to get.
  92.             subjectArray = handle to subject array.
  93.             index = index in subject array to store first subject.
  94.             strings = handle to strings block.
  95.             *nextStringOffset = offset of next available location in
  96.                 strings block.
  97.     
  98.     Exit:    function result = error code.
  99.             *numFetched = number of subjects fetched.
  100.             *nextStringOffset updated.
  101.     
  102.     The caller must preallocate memory in the subject array for at least
  103.     (last-first+1) new subjects.
  104.     
  105.     If the group does not exist the function result is true and 
  106.     *numFetched=0.
  107. ----------------------------------------------------------------------------*/
  108.  
  109. static OSErr GetSubjects (char *newsGroup, long first, long last, 
  110.     TSubject **subjectArray, short index,
  111.     Handle strings, long *nextStringOffset,
  112.     short *numFetched)
  113. {
  114.     THeader **headers = nil; 
  115.     long numHeaders;
  116.     TSubject theSubject;
  117.     THeader *pHeader, *pHeaderEnd;
  118.     TSubject *pSubject;
  119.     Handle str = nil;
  120.     long strLen;
  121.     long offset;
  122.     OSErr err = noErr;
  123.  
  124.     err = GetHeaders(newsGroup, "SUBJECT", first, last, &headers, &str, &numHeaders);
  125.     if (err != noErr) return err;
  126.     if (headers == nil) {
  127.         *numFetched = 0;
  128.         return noErr;
  129.     }
  130.     
  131.     strLen = MyGetHandleSize(str);
  132.     offset = *nextStringOffset;
  133.     err = MyHandAndHand(str, strings);
  134.     if (err != noErr) goto exit;
  135.     *nextStringOffset += strLen;
  136.     
  137.     pHeaderEnd = *headers + numHeaders;
  138.     pSubject = *subjectArray + index;
  139.     for (pHeader = *headers; pHeader < pHeaderEnd; pHeader++, pSubject++, index++) {
  140.         theSubject.subjectOffset = pHeader->offset + offset;
  141.         theSubject.authorOffset = -1;
  142.         theSubject.number = pHeader->number;
  143.         theSubject.collapsed = gPrefs.showThreadsCollapsed;
  144.         theSubject.read = false;
  145.         theSubject.inList = true;
  146.         theSubject.drawTriangleFilled = false;
  147.         theSubject.onlyRedrawTriangle = false;
  148.         theSubject.onlyRedrawCheck = false;
  149.         *pSubject = theSubject;
  150.     }
  151.  
  152.     MyDisposeHandle(headers);
  153.     MyDisposeHandle(str);
  154.     *numFetched = numHeaders;
  155.     return noErr;
  156.     
  157. exit:
  158.  
  159.     MyDisposeHandle(str);
  160.     MyDisposeHandle(headers);
  161.     return err;
  162. }
  163.  
  164.  
  165.  
  166. /*----------------------------------------------------------------------------
  167.     GetAuthors
  168.     
  169.     Get a range of author headers from the news server and store them in
  170.     a subject array.
  171.     
  172.     Entry:    newsGroup = name of the group.
  173.             first = first article number to get.
  174.             last = last article number to get.
  175.             subjectArray = handle to subject array.
  176.             index = starting index in subject array.
  177.             num = number of subject records.
  178.             strings = handle to strings block.
  179.             *nextStringOffset = offset of next available location in
  180.                 strings block.
  181.     
  182.     Exit:    function result = error code.
  183.             *nextStringOffset updated.
  184.     
  185.     GetSubjects must be called before GetAuthors to initialize the
  186.     subject array elements.
  187. ----------------------------------------------------------------------------*/
  188.  
  189. static OSErr GetAuthors (char *newsGroup, long first, long last, 
  190.     TSubject **subjectArray, short index, short num,
  191.     Handle strings, long *nextStringOffset)
  192. {
  193.     THeader **headers = nil; 
  194.     long numHeaders;
  195.     THeader *pHeader, *pHeaderEnd;
  196.     TSubject *pSubject, *pSubjectEnd;
  197.     Handle str = nil;
  198.     long strLen;
  199.     long offset;
  200.     OSErr err = noErr;
  201.  
  202.     err = GetHeaders(newsGroup, "FROM", first, last, &headers, &str, &numHeaders);
  203.     if (err != noErr) return err;
  204.     if (headers == nil) return noErr;
  205.     
  206.     strLen = MyGetHandleSize(str);
  207.     offset = *nextStringOffset;
  208.     err = MyHandAndHand(str, strings);
  209.     if (err != noErr) goto exit;
  210.     *nextStringOffset += strLen;
  211.     
  212.     pHeader = *headers;
  213.     pHeaderEnd = *headers + numHeaders;
  214.     pSubject = *subjectArray + index;
  215.     pSubjectEnd = pSubject + num;
  216.     while (pHeader < pHeaderEnd && pSubject < pSubjectEnd) {
  217.         if (pHeader->number < pSubject->number) {
  218.             pHeader++;
  219.         } else if (pHeader->number > pSubject->number) {
  220.             pSubject++;
  221.         } else {
  222.             pSubject->authorOffset = pHeader->offset + offset;
  223.             pHeader++;
  224.             pSubject++;
  225.         }
  226.     }
  227.     
  228.     MyDisposeHandle(headers);
  229.     MyDisposeHandle(str);
  230.     return noErr;
  231.     
  232. exit:
  233.  
  234.     MyDisposeHandle(headers);
  235.     MyDisposeHandle(str);
  236.     return err;
  237. }
  238.  
  239.  
  240.  
  241. /*----------------------------------------------------------------------------
  242.     GetSubjectsAndAuthorsFromNet 
  243.     
  244.     Get unread subjects and authors for a group from the NNTP server.
  245.     
  246.     Entry:    groupName = the group name.
  247.             theGroup = pointer to the group record.
  248.             groupKind = kind of group (kFullGroup, kNewGroup, or kUserGroup).
  249.             maxFetch = maximum number of articles to fetch.
  250.             
  251.     Exit:    function result = error code.
  252.             *subjectArray = handle to subject array, with the fields
  253.                 subjectOffset, authorOffset, number, read, and inList initialized.
  254.             *numSubjects = number of subjects fetched.
  255.             *subjectStrings = handle to subject strings.
  256.             *firstFetched = article number of first article fetched.
  257.             
  258.     If the group does not exist, the function result is true and 
  259.     *numSubjects = 0. In other words, a deleted group is treated the
  260.     same as a group with no unread articles.
  261. ----------------------------------------------------------------------------*/
  262.  
  263. static OSErr GetSubjectsAndAuthorsFromNet (char *groupName, TGroup *theGroup, 
  264.     TGroupWindowKind groupKind, short maxFetch,
  265.     TSubject ***subjectArray, short *numSubjects, 
  266.     Handle *subjectStrings, long *firstFetched)
  267. {
  268.     TSubject **array = nil;
  269.     short index = 0;
  270.     Handle strings = nil;
  271.     OSErr err = noErr;
  272.     long nextStringOffset = 0;
  273.     TUnread **unread;
  274.     long first, last, numToFetch, totNumToFetch;
  275.     short numAllocated;
  276.     short numFetched;
  277.     
  278.     err = MyNewHandle(100*sizeof(TSubject), &array);
  279.     if (err != noErr) goto exit;
  280.     numAllocated = 100;
  281.     err = MyNewHandle(0, &strings);
  282.     if (err != noErr) goto exit;
  283.     
  284.     if (groupKind == kUserGroup) {
  285.         unread = theGroup->unread;
  286.         totNumToFetch = theGroup->numUnread;
  287.         first = (**unread).firstUnread;
  288.         last = (**unread).lastUnread;
  289.         if (totNumToFetch > maxFetch) {
  290.             while (unread != nil) {
  291.                 totNumToFetch -= last - first + 1;
  292.                 if (totNumToFetch <= maxFetch) {
  293.                     first = last - (maxFetch - totNumToFetch) + 1;
  294.                     break;
  295.                 }
  296.                 unread = (**unread).next;
  297.                 if (unread != nil) {
  298.                     first = (**unread).firstUnread;
  299.                     last = (**unread).lastUnread;
  300.                 }
  301.             }
  302.         }
  303.         *firstFetched = first;
  304.         while (unread != nil) {
  305.             numToFetch = last - first + 1;
  306.             if (index + numToFetch > numAllocated) {
  307.                 numAllocated = index + numToFetch + 100;
  308.                 err = MySetHandleSize(array, numAllocated*sizeof(TSubject));
  309.                 if (err != noErr) goto exit;
  310.             }
  311.             err = GetSubjects(groupName, first, last, array, index, strings,
  312.                 &nextStringOffset, &numFetched);
  313.             if (err != noErr) goto exit;
  314.             if (gPrefs.showAuthors) {
  315.                 err = GetAuthors(groupName, first, last, array, index, numFetched, strings,
  316.                     &nextStringOffset);
  317.                 if (err != noErr) goto exit;
  318.             }
  319.             index += numFetched;
  320.             unread = (**unread).next;
  321.             if (unread != nil) {
  322.                 first = (**unread).firstUnread;
  323.                 last = (**unread).lastUnread;
  324.             }
  325.         }
  326.     } else {
  327.         first = theGroup->firstMess;
  328.         last = theGroup->lastMess;
  329.         numToFetch = last - first + 1;
  330.         if (numToFetch > maxFetch) {
  331.             numToFetch = maxFetch;
  332.             first = last - numToFetch + 1;
  333.         }
  334.         *firstFetched = first;
  335.         if (numToFetch > numAllocated) {
  336.             numAllocated = numToFetch;
  337.             err = MySetHandleSize(array, numAllocated*sizeof(TSubject));
  338.             if (err != noErr) goto exit;
  339.         }
  340.         err = GetSubjects(groupName, first, last, array, index, strings,
  341.             &nextStringOffset, &numFetched);
  342.         if (err != noErr) goto exit;
  343.         if (gPrefs.showAuthors) {
  344.             err = GetAuthors(groupName, first, last, array, index, numFetched, strings,
  345.                 &nextStringOffset);
  346.             if (err != noErr) goto exit;
  347.         }
  348.         index = numFetched;
  349.     }
  350.     
  351.     MySetHandleSize(array, index*sizeof(TSubject));
  352.     MySetHandleSize(strings, nextStringOffset);
  353.  
  354.     *subjectArray = array;
  355.     *numSubjects = index;
  356.     *subjectStrings = strings;
  357.     return noErr;
  358.  
  359. exit:
  360.  
  361.     MyDisposeHandle(array);
  362.     MyDisposeHandle(strings);
  363.     return err;
  364. }
  365.  
  366.  
  367.  
  368. /*----------------------------------------------------------------------------
  369.     FixHeight 
  370.     
  371.     Round down window height to an exact multiple of lines.
  372.             
  373.     Entry:    wind = pointer to subject window.
  374.             *height = window height.
  375.             
  376.     Exit:    *height = adjusted window height
  377. ----------------------------------------------------------------------------*/
  378.  
  379. static void FixHeight (WindowPtr wind, short *height)
  380. {
  381.     TWindow **info;
  382.     short panelHeight, lineHeight, adjust;
  383.  
  384.     info = (TWindow**)GetWRefCon(wind);
  385.     panelHeight = (**info).panelHeight;
  386.     lineHeight = GetFontLineHeight(wind);
  387.     adjust = panelHeight + 15;
  388.     *height = (*height - adjust) / lineHeight * lineHeight + adjust;
  389. }
  390.  
  391.  
  392.  
  393. /*----------------------------------------------------------------------------
  394.     MinHeight 
  395.     
  396.     Compute the minimum height of a subject window.
  397.             
  398.     Entry:    wind = pointer to subject window.
  399. ----------------------------------------------------------------------------*/
  400.  
  401. static short MinHeight (WindowPtr wind)
  402. {
  403.     TWindow **info;
  404.     short lineHeight, height, extra;
  405.     
  406.     info = (TWindow**)GetWRefCon(wind);
  407.     lineHeight = GetFontLineHeight(wind);
  408.     extra = lineHeight + 15;
  409.     if (extra < 65) extra = 65 + lineHeight;
  410.     height = (**info).panelHeight + extra;
  411.     FixHeight(wind, &height);
  412.     return height;
  413. }
  414.  
  415.  
  416.  
  417. /*----------------------------------------------------------------------------
  418.     Scroll 
  419.     
  420.     Scroll a subject window.
  421.             
  422.     Entry:    wind = pointer to subject window.
  423.             part = part code.
  424. ----------------------------------------------------------------------------*/
  425.  
  426. static void Scroll (WindowPtr wind, short part)
  427. {
  428.     TWindow **info;
  429.     ListHandle theList;
  430.     short height, numCells;
  431.     Cell theCell;
  432.     
  433.     info = (TWindow**)GetWRefCon(wind);
  434.     theList = (**info).theList;
  435.     height = (**theList).visible.bottom - (**theList).visible.top;
  436.     numCells = (**theList).dataBounds.bottom;
  437.     switch (part) {
  438.         case inUpButton:
  439.         case inDownButton:
  440.             SetPt(&theCell, 0, 0);
  441.             if (part == inUpButton) {
  442.                 theCell.v = GetFirstSelectedCell(theList);
  443.                 theCell.v--;
  444.                 if (theCell.v < 0) theCell.v = 0;
  445.             } else { 
  446.                 theCell.v = GetLastSelectedCell(theList);
  447.                 theCell.v++;
  448.                 if (theCell.v >= numCells) theCell.v = numCells-1;
  449.             }
  450.             SelectSingleListItem(theList, theCell);
  451.             HandleUpdate(wind);
  452.             MyLAutoScroll(theList);
  453.             break;
  454.         case inPageUp:
  455.             MyLScroll(-(height - 1), theList);
  456.             break;
  457.         case inPageDown:
  458.             MyLScroll(height - 1, theList);
  459.             break;
  460.         case kScrollToHome:
  461.             MyLScroll(-numCells, theList);
  462.             break;
  463.         case kScrollToEnd:
  464.             MyLScroll(numCells, theList);
  465.             break;
  466.     }
  467.     KillBalloon();
  468. }
  469.  
  470.  
  471.  
  472. /*----------------------------------------------------------------------------
  473.     ResizeContents 
  474.     
  475.     Adjust a group window's contents after a window size change (grow
  476.     or zoom).
  477.             
  478.     Entry:    wind = pointer to group window.
  479. ----------------------------------------------------------------------------*/
  480.  
  481. static void ResizeContents (WindowPtr wind)
  482. {
  483.     TWindow **info;
  484.     short width, height, panelHeight;
  485.     ListHandle theList;
  486.     Point x;
  487.  
  488.     info = (TWindow**)GetWRefCon(wind);
  489.     panelHeight = (**info).panelHeight;
  490.     theList = (**info).theList;
  491.     width = wind->portRect.right;
  492.     height = wind->portRect.bottom;
  493.  
  494.     LSize(width-15, height-15-panelHeight, theList);
  495.     SetPt(&x, width-15, (**theList).cellSize.v);
  496.     LCellSize(x, theList);
  497.     
  498.     InvalRect(&wind->portRect);
  499.     
  500.     RecordNewLockedWindowPosition(wind, &gPrefs.subjectWindLocn);
  501. }
  502.  
  503.  
  504.  
  505. /*----------------------------------------------------------------------------
  506.     MakeNewWindow 
  507.     
  508.     Create a new subject window.
  509.     
  510.     Entry:    title = window title.
  511.             
  512.     Exit:    function result = error code.
  513.             *theWindow = pointer to new window.
  514. ----------------------------------------------------------------------------*/
  515.  
  516. static OSErr MakeNewWindow (StringPtr title, WindowPtr *theWindow)
  517. {
  518.     short width, height;
  519.     WindowPtr wind = nil;
  520.     TWindow **info;
  521.     ListHandle theList;
  522.     Point thePt;
  523.     Rect listRect, sizeRect;
  524.     short wid, checkMarkWidth, panelHeight;
  525.     short tHeight, tWidth, x;
  526.     FontInfo fInfo;
  527.     TextStyle savedStyle;
  528.     OSErr err;
  529.     GrafPtr port;
  530.     
  531.     GetPort(&port);
  532.     
  533.     MyICReadSharedPrefs(kICListFont);
  534.  
  535.     err = CreateNewWindow(kSubject, title, gPrefs.listFont, gPrefs.listSize, &wind);
  536.     if (err != noErr) return err;
  537.     SetPort(wind);
  538.     info = (TWindow**)GetWRefCon(wind);
  539.     panelHeight = GetFontLineHeight(wind) + 9;
  540.     (**info).panelHeight = panelHeight;
  541.     (**info).authorsShown = gPrefs.showAuthors;
  542.     width = kMinWindowWidth;
  543.     height = MinHeight(wind);
  544.     RestoreLockedWindowPosition(wind, width, height, gPrefs.subjectWindPosLocked,
  545.         &gPrefs.subjectWindLocn);
  546.     width = wind->portRect.right;
  547.     height = wind->portRect.bottom;
  548.  
  549.     SetPt(&thePt, 0, 0);
  550.     SetRect(&sizeRect, 0, 0, 1, 0);
  551.     listRect = wind->portRect;
  552.     listRect.top += panelHeight;
  553.     listRect.right -= 15;
  554.     listRect.bottom -= 15;
  555.     theList = MyLNew(&listRect, &sizeRect, thePt, kLDEFProc, wind, false, true, false, true);
  556.     (**theList).indent.h = 4;
  557.     (**theList).selFlags |= lNoNilHilite;
  558.     (**theList).refCon = (long)gListDefFuncUPP;
  559.     (**info).theList = theList;
  560.     (**info).vScroll = (**theList).vScroll;
  561.     
  562.     GetFontInfo(&fInfo);
  563.     wid = CharWidth('9');
  564.     GetPortTextStyle(&savedStyle);
  565.     TextFont(systemFont);
  566.     checkMarkWidth = CharWidth(checkMark);
  567.     SetPortTextStyle(&savedStyle);
  568.     tWidth = fInfo.ascent;
  569.     if ((tWidth & 1) == 1) tWidth--;
  570.     tHeight = tWidth >> 1; 
  571.     (**info).minusSignHCoord = ((tWidth - CharWidth('-')) >> 1) + 2;
  572.     (**info).threadCountHCoord = x = 3*wid + tWidth;
  573.     (**info).checkHCoord = x = x + 2*wid;
  574.     if ((**info).authorsShown) {
  575.         (**info).authorHCoord = x = x + 2*checkMarkWidth;
  576.         (**info).authorWidth = 16*wid;
  577.         (**info).subjectHCoord = x + 18*wid;
  578.     } else {
  579.         (**info).subjectHCoord = (**info).authorHCoord = x = x + 2*checkMarkWidth;
  580.     }
  581.     (**info).collapseTriangle = OpenPoly();
  582.         MoveTo(0,0);
  583.         LineTo(0,tWidth);
  584.         LineTo(tHeight,tHeight);
  585.         LineTo(0,0);
  586.     ClosePoly();
  587.     (**info).expandTriangle = OpenPoly();
  588.         MoveTo(0,0);
  589.         LineTo(tWidth,0);
  590.         LineTo(tHeight,tHeight);
  591.         LineTo(0,0);
  592.     ClosePoly();
  593.     
  594.     (**info).autoExpandedThread = -1;
  595.     
  596.     *theWindow = wind;
  597.     SetPort(port);
  598.     return noErr;
  599. }
  600.  
  601.  
  602.  
  603. /*----------------------------------------------------------------------------
  604.     RedrawSubjectWindowUnreadCount
  605.     
  606.     Force the panel are unread article count to be updated in a subject window.
  607.         
  608.     Entry:    wind = pointer to suject list window.
  609. ----------------------------------------------------------------------------*/
  610.  
  611. void RedrawSubjectWindowUnreadCount (WindowPtr wind)
  612. {
  613.     TWindow **info;
  614.     GrafPtr port;
  615.     Rect r;
  616.  
  617.     GetPort(&port);
  618.     SetPort(wind);
  619.     info = (TWindow**)GetWRefCon(wind);
  620.     r = wind->portRect;
  621.     r.bottom = (**info).panelHeight - 4;
  622.     InvalRect(&r);
  623.     SetPort(port);
  624. }
  625.  
  626.  
  627.  
  628. /*----------------------------------------------------------------------------
  629.     OpenGroupCell 
  630.     
  631.     Open one subject window for a cell in a group list window.
  632.     
  633.     Entry:    groupWind = pointer to group list window.
  634.             theCell = the cell in the group list window to be opened.
  635.             maxFetch = maximum number of articles to fetch.
  636.             
  637.     Exit:    function result = error code.
  638.             *hasArts = true if the group exists, has articles, and the window
  639.                 was opened.
  640. ----------------------------------------------------------------------------*/
  641.  
  642. OSErr OpenGroupCell (WindowPtr groupWind, Cell theCell, short maxFetch, Boolean *hasArts)
  643. {
  644.     WindowPtr wind = nil;
  645.     TWindow **groupInfo, **info;
  646.     ListHandle theList;
  647.     TGroup **groupArray, theGroup;
  648.     TSubject **subjectArray = nil;
  649.     short numSubjects = 0;
  650.     Handle subjectStrings = nil;
  651.     CStr255 groupName;
  652.     short cellDataLen, index;
  653.     TGroupWindowKind groupKind;
  654.     Cell childCell;
  655.     CStr255 statusMsg;
  656.     long firstFetched;
  657.     OSErr err = noErr;
  658.     Boolean groupExists;
  659.     GrafPtr port;
  660.     
  661.     GetPort(&port);
  662.     
  663.     *hasArts = true;
  664.     
  665.     /* Check to see if the subject window is already open. Bring it
  666.        to the front if it is. */
  667.  
  668.     if ((wind = FindChild(groupWind, theCell)) != nil) {
  669.         MySelectWindow(wind);
  670.         return noErr;
  671.     }
  672.     
  673.     /* Initialize. */
  674.     
  675.     groupInfo = (TWindow**)GetWRefCon(groupWind);
  676.     groupKind = (**groupInfo).groupKind;
  677.     theList = (**groupInfo).theList;
  678.     groupArray = (**groupInfo).groupArray;
  679.     cellDataLen = 2;
  680.     LGetCell(&index, &cellDataLen, theCell, theList);
  681.     theGroup = (*groupArray)[index];
  682.     strcpy(groupName, *gGroupNames + theGroup.nameOffset);
  683.     if (groupKind == kUserGroup && theGroup.numUnread == 0) goto exit1;
  684.     
  685.     if (groupKind == kUserGroup) (**groupInfo).changed = true;
  686.     
  687.     /* Display the status window. */    
  688.     
  689.     if (gPrefs.showAuthors) {
  690.         GetCString(kStrGettingSubjectsAndAuthorsStatusMsg, statusMsg);
  691.     } else {
  692.         GetCString(kStrGetSubjectsStatusMsg, statusMsg);
  693.     }
  694.     strcat(statusMsg, groupName);
  695.     statusMsg[255] = 0;
  696.     err = DisplayStatusMessage(statusMsg);
  697.     if (err != noErr) goto exit2;
  698.     
  699.     /* If we are opening a cell in the full group list window or the new groups
  700.        window, first call GetGroupArticleRange to get the [low,high] article range
  701.        for the group. */
  702.  
  703.     if (groupKind != kUserGroup) {
  704.         err = GetGroupArticleRange(&theGroup, &groupExists);
  705.         if (err != noErr) goto exit2;
  706.         if (!groupExists || theGroup.numUnread == 0) goto exit1;
  707.     }
  708.     
  709.     /* Get the subjects and authors from the server. */
  710.     
  711.     err = GetSubjectsAndAuthorsFromNet(groupName, &theGroup, groupKind, maxFetch,
  712.         &subjectArray, &numSubjects, &subjectStrings, &firstFetched);
  713.     if (err != noErr) goto exit2;
  714.     if (numSubjects == 0) goto exit1;
  715.     
  716.     /* Create the subject window. */
  717.     
  718.     c2pstr(groupName);
  719.     err = MakeNewWindow((StringPtr)groupName, &wind);
  720.     if (err != noErr) goto exit2;
  721.     SetPort(wind);
  722.     info = (TWindow**)GetWRefCon(wind);
  723.     
  724.     (**info).subjectArray = subjectArray;
  725.     subjectArray = nil;
  726.     (**info).numSubjects = numSubjects;
  727.     (**info).firstFetched = firstFetched;
  728.     (**info).strings = subjectStrings;
  729.     subjectStrings = nil;
  730.     (**info).parentWindow = groupWind;
  731.     (**info).parentGroup = index;
  732.     (**info).groupNameOffset = theGroup.nameOffset;
  733.     
  734.     /* Build the threads. */
  735.  
  736.     err = BuildThreads(wind);
  737.     if (err != noErr) goto exit2;
  738.     
  739.     /* Finish initializing the new subject window. */
  740.  
  741.     err = AddChild(groupWind, wind);
  742.     if (err != noErr) goto exit2;
  743.     SetPt(&childCell, 0, 0);
  744.     MyLSetSelect(true, childCell, (**info).theList);
  745.     if (!(**info).windPosLocked) {
  746.         err = DoZoom(wind, inZoomOut);
  747.         if (err != noErr) goto exit2;
  748.     }
  749.     
  750.     /* For a user group list, update the unread list and the number
  751.        of unread articles counter. */
  752.     
  753.     if (groupKind == kUserGroup) {
  754.         err = UpdateUnreadList(wind);
  755.         if (err != noErr) goto exit2;
  756.         (*groupArray)[index].onlyRedrawCount = true;
  757.         LDraw(theCell, theList);
  758.         (*groupArray)[index].onlyRedrawCount = false;
  759.     }
  760.     
  761.     /* Show the new subject window. */
  762.     
  763.     MyShowWindow(wind);
  764.     
  765.     SetPort(port);
  766.     return noErr;
  767.     
  768. exit1:
  769.  
  770.     MyDisposeHandle(subjectArray);
  771.     MyDisposeHandle(subjectStrings);
  772.     if (groupKind == kUserGroup) {
  773.         DisposeGroupUnreadList(&theGroup);
  774.         (*groupArray)[index] = theGroup;
  775.         (*groupArray)[index].onlyRedrawCount = true;
  776.         LDraw(theCell, theList);
  777.         (*groupArray)[index].onlyRedrawCount = false;
  778.     }
  779.     *hasArts = false;
  780.     SetPort(port);
  781.     return noErr;
  782.     
  783. exit2:
  784.  
  785.     if (wind != nil) {
  786.         /* Hack to avoid marking articles read in mark.c/UpdateUnreadList */
  787.         (**info).firstFetched = 0x7fffffff;
  788.         (**info).numSubjectsInList = 0;
  789.     }
  790.     DoClose(wind);
  791.     MyDisposeHandle(subjectArray);
  792.     MyDisposeHandle(subjectStrings);
  793.     SetPort(port);
  794.     return err;
  795. }
  796.  
  797.  
  798.  
  799. /*----------------------------------------------------------------------------
  800.     OpenSelectedCells 
  801.     
  802.     Open all the selected articles in a subject list window.
  803.     
  804.     Entry:    wind = pointer to subject list window.
  805.     
  806.     Exit:    function result = error code.
  807. ----------------------------------------------------------------------------*/
  808.  
  809. static OSErr OpenSelectedCells (WindowPtr wind)
  810. {
  811.     Cell theCell;
  812.     TWindow **info;
  813.     ListHandle theList;
  814.     short *p, *pBegin, numSelected=0, numOpened=0;
  815.     OSErr err = noErr;
  816.     WindowPtr child;
  817.     
  818.     info = (TWindow**) GetWRefCon(wind);
  819.     theList = (**info).theList;
  820.     
  821.     SetPt(&theCell, 0, (**theList).dataBounds.bottom-1);
  822.     while (theCell.v >= 0) {
  823.         pBegin = (**theList).cellArray;
  824.         p = pBegin + theCell.v;
  825.         while (*p >= 0 && p >= pBegin) p--;
  826.         theCell.v = p - pBegin;
  827.         if (p >= pBegin) {
  828.             err = OpenSubjectCell(wind, theCell, 1, nil, &child);
  829.             if (err != noErr) return err;
  830.             numSelected++;
  831.             if (child != nil) numOpened++;
  832.         }
  833.         theCell.v--;
  834.     }
  835.     if (numOpened < numSelected) {
  836.         if (numSelected == 1) {
  837.             NoteMessageNumber(kStrSelArticleDoesntExist);
  838.         } else {
  839.             if (numOpened == 0) {
  840.                 NoteMessageNumber(kStrNoneExist);
  841.             } else {
  842.                 NoteMessageNumber(kStrSomeDontExist);
  843.             }
  844.         }
  845.     }
  846.     return noErr;
  847. }
  848.  
  849.  
  850.  
  851. /*----------------------------------------------------------------------------
  852.     CancelSubjectCell 
  853.     
  854.     Cancel the article corresponding to a cell in a subject list window.
  855.     
  856.     Entry:    wind = pointer to subject list window.
  857.             cell = cell to cancel.
  858.             statusMsg = status message, C-format.
  859.     
  860.     Exit:    function result = error code.
  861.             *canceled = true if canceled or article does not exist,
  862.                 false if user is not owner of article.
  863. ----------------------------------------------------------------------------*/
  864.  
  865. static OSErr CancelSubjectCell (WindowPtr wind, Cell theCell, char *statusMsg,
  866.     Boolean *canceled)
  867. {
  868.     TWindow **info;
  869.     ListHandle theList;
  870.     TSubject **subjectArray;
  871.     short index, cellDataLen;
  872.     long number;
  873.     CStr255 groupName, idStr;
  874.     OSErr err = noErr;
  875.     Handle text = nil;
  876.     Handle groups = nil;
  877.     long length;
  878.     Boolean attachedFile;
  879.     
  880.     info = (TWindow**)GetWRefCon(wind);
  881.     theList = (**info).theList;
  882.     subjectArray = (**info).subjectArray;
  883.     cellDataLen = 2;
  884.     LGetCell(&index, &cellDataLen, theCell, theList);
  885.     number = (*subjectArray)[index].number;
  886.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  887.     
  888.     err = GetArticle(nil, 0, groupName, number, nil, "HEAD", false, false, &text, 
  889.         &length, &attachedFile);
  890.     if (err != noErr) return err;
  891.     
  892.     if (text == nil) {
  893.         *canceled = true;
  894.         return noErr;
  895.     }
  896.     
  897.     if (!UserIsPoster(text)) {
  898.         *canceled = false;
  899.         return noErr;
  900.     }
  901.     
  902.     if (!FindHeaderCString(text, "Message-ID", idStr, sizeof(idStr))) goto exit1;
  903.     
  904.     err = FindHeaderHandle(text, "Newsgroups", &groups);
  905.     if (err != noErr) goto exit2;
  906.     if (groups == nil) goto exit3;
  907.     
  908.     MyDisposeHandle(text);
  909.     text = nil;
  910.     
  911.     err = CancelArticle(idStr, groups, statusMsg);
  912.     if (err != noErr) goto exit2;
  913.     
  914.     *canceled = true;
  915.     MyDisposeHandle(groups);
  916.     return noErr;
  917.     
  918. exit1:
  919.  
  920.     MyDisposeHandle(text);
  921.     MyDisposeHandle(groups);
  922.     ErrorMessageNumber(kStrNoMsgID);
  923.     return userCanceledErr;
  924.     
  925. exit2:
  926.  
  927.     MyDisposeHandle(text);
  928.     MyDisposeHandle(groups);
  929.     return err;
  930.     
  931. exit3:
  932.  
  933.     MyDisposeHandle(text);
  934.     MyDisposeHandle(groups);
  935.     ErrorMessageNumber(kStrNoNewsgroupsHeader);
  936.     return userCanceledErr;
  937. }
  938.  
  939.  
  940.  
  941. /*----------------------------------------------------------------------------
  942.     EncodedTextBeginLineIsRequired
  943.     
  944.     Determine whether or not an article requires a BinHex or uuencode
  945.     beginning "flag" line for attached binaries.
  946.     
  947.     Entry:    subjectArray = handle to subject array.
  948.             index = index in subject array.
  949.                 
  950.     Exit:    function result = true if flag line required.
  951. ----------------------------------------------------------------------------*/
  952.  
  953. Boolean EncodedTextBeginLineIsRequired (TSubject **subjectArray, short index)
  954. {
  955.     TSubject *s;
  956.     
  957.     s = &(*subjectArray)[index];
  958.     return s->partNum == 0 || s->partNum == 0x7fff ||
  959.         (!s->incomplete && s->threadOrdinal == 1);
  960. }
  961.  
  962.  
  963.  
  964. /*----------------------------------------------------------------------------
  965.     OpenMessageWindowForOneCell 
  966.     
  967.     Open a reply, forward, or redirect window for a cell in a subject list window.
  968.     
  969.     Entry:    wind = pointer to subject list window.
  970.             cell = cell in window.
  971.             modifiers = modifiers field from event record.
  972.             func = pointer to function to open one message window.
  973.     
  974.     Exit:    function result = error code.
  975. ----------------------------------------------------------------------------*/
  976.  
  977. static OSErr OpenMessageWindowForOneCell (WindowPtr wind, Cell theCell,
  978.     short modifiers, OSErr (*func)(Handle, Handle, long, long, Boolean))
  979. {
  980.     TWindow **info;
  981.     ListHandle theList;
  982.     TSubject **subjectArray;
  983.     short index, cellDataLen;
  984.     long number;
  985.     CStr255 groupName;
  986.     OSErr err = noErr;
  987.     Handle text = nil;
  988.     long length;
  989.     Boolean attachedFile, flagReqd;
  990.     
  991.     info = (TWindow**)GetWRefCon(wind);
  992.     theList = (**info).theList;
  993.     subjectArray = (**info).subjectArray;
  994.     cellDataLen = 2;
  995.     LGetCell(&index, &cellDataLen, theCell, theList);
  996.     number = (*subjectArray)[index].number;
  997.     flagReqd = EncodedTextBeginLineIsRequired(subjectArray, index);
  998.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  999.     
  1000.     err = GetArticle(nil, 0, groupName, number, nil, "ARTICLE", true, 
  1001.         flagReqd, &text, &length, &attachedFile);
  1002.     if (err != noErr) return err;
  1003.     
  1004.     if (text == nil) return noErr;
  1005.  
  1006.      err = (*func)(text, nil, 0, 0, (modifiers & optionKey) != 0);
  1007.  
  1008.     MyDisposeHandle(text);
  1009.     return err;
  1010. }
  1011.  
  1012.  
  1013.  
  1014. /*----------------------------------------------------------------------------
  1015.     OpenMessageWindowForAllSelectedCells 
  1016.     
  1017.     Open a reply, forward, or redirect message window for all selected
  1018.     cells in a subject window.
  1019.             
  1020.     Entry:    wind = pointer to subject window.
  1021.             modifiers = modifiers field from event record.
  1022.             func = pointer to function to open one message window.
  1023.             
  1024.     Exit:    function result = error code.
  1025. ----------------------------------------------------------------------------*/
  1026.  
  1027. static OSErr OpenMessageWindowForAllSelectedCells (WindowPtr wind, 
  1028.     short modifiers, OSErr (*func)(Handle, Handle, long, long, Boolean))
  1029. {
  1030.     Cell theCell;
  1031.     TWindow **info;
  1032.     ListHandle theList;
  1033.     short *p, *pBegin;
  1034.     OSErr err = noErr;
  1035.     
  1036.     info = (TWindow**) GetWRefCon(wind);
  1037.     theList = (**info).theList;
  1038.     
  1039.     SetPt(&theCell, 0, (**theList).dataBounds.bottom-1);
  1040.     while (theCell.v >= 0) {
  1041.         pBegin = (**theList).cellArray;
  1042.         p = pBegin + theCell.v;
  1043.         while (*p >= 0 && p >= pBegin) p--;
  1044.         theCell.v = p - pBegin;
  1045.         if (p >= pBegin) {
  1046.             err = OpenMessageWindowForOneCell(wind, theCell, modifiers, func);
  1047.             if (err != noErr) return err;
  1048.         }
  1049.         theCell.v--;
  1050.     }
  1051.     return noErr;
  1052. }
  1053.  
  1054.  
  1055.  
  1056. /*----------------------------------------------------------------------------
  1057.     FormFileName 
  1058.     
  1059.     Form the default file name for saving articles.
  1060.             
  1061.     Entry:    wind = pointer to subject window.
  1062.             theCell = cell in window.
  1063.     
  1064.     Exit:    function result = error code.
  1065.             fileName = default file name.
  1066.             
  1067.     The file name is formed from the subject of the first selected cell
  1068.     which is >= theCell.
  1069. ----------------------------------------------------------------------------*/
  1070.  
  1071. static OSErr FormFileName (WindowPtr wind, Cell theCell, Str31 fileName)
  1072. {
  1073.     TWindow **info;
  1074.     TSubject **subjectArray;
  1075.     ListHandle theList;
  1076.     Str255 subject;
  1077.     long subjectOffset;
  1078.     short cellDataLen, index;
  1079.     Handle strings;
  1080.     
  1081.     info = (TWindow**)GetWRefCon(wind);
  1082.     subjectArray = (**info).subjectArray;
  1083.     theList = (**info).theList;
  1084.     strings = (**info).strings;
  1085.     if (!LGetSelect(true, &theCell, theList)) return userCanceledErr;
  1086.     cellDataLen = 2;
  1087.     LGetCell(&index, &cellDataLen, theCell, theList);
  1088.     subjectOffset = (*subjectArray)[index].subjectOffset;
  1089.     strcpy((char*)subject, *strings + subjectOffset);
  1090.     c2pstr((char*)subject);
  1091.     MakeLegalFileName(subject, fileName);
  1092.     return noErr;
  1093. }
  1094.  
  1095.  
  1096.  
  1097. /*----------------------------------------------------------------------------
  1098.     SaveOneCellToFile 
  1099.     
  1100.     Save a single cell to a file.
  1101.             
  1102.     Entry:    wind = pointer to subject window.
  1103.             theCell = cell to save.
  1104.             refNum = file ref num.
  1105.             statusMsg = status message.
  1106.             *artNum = article ordinal within sequence of articles
  1107.                 being saved (1,2,3,...).
  1108.             numArticlestoSave = total number of articles being saved.
  1109.             writeSeparator = true to write separator before first saved
  1110.                 article.
  1111.             saveEncodedText = true to save encoded text.
  1112.     
  1113.     Exit:    function result = error code.
  1114.             *artNum updated.
  1115. ----------------------------------------------------------------------------*/
  1116.  
  1117. static OSErr SaveOneCellToFile (WindowPtr wind, Cell theCell, short refNum, 
  1118.     char *statusMsg, short *artNum, short numArticlesToSave, 
  1119.     Boolean writeSeparator, Boolean saveEncodedText)
  1120. {
  1121.     TWindow **info;
  1122.     ListHandle theList;
  1123.     TSubject **subjectArray;
  1124.     short index, cellDataLen;
  1125.     long number;
  1126.     CStr255 groupName, msg;
  1127.     OSErr err = noErr;
  1128.     TAttachedFileKind fileKind;
  1129.     char *separator;
  1130.     long length;
  1131.     Boolean truncateIfAttachedFile, flagReqd;
  1132.     
  1133.     info = (TWindow**)GetWRefCon(wind);
  1134.     theList = (**info).theList;
  1135.     subjectArray = (**info).subjectArray;
  1136.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  1137.     cellDataLen = 2;
  1138.     LGetCell(&index, &cellDataLen, theCell, theList);
  1139.     
  1140.     while (true) {
  1141.         if (writeSeparator) {
  1142.             separator = 
  1143.                 "\r----------------------------------------------------------------------\r\r";
  1144.             length = strlen(separator);
  1145.             err = MyFSWriteNoCache(refNum, &length, separator, nil);
  1146.             if (err != noErr) return err;
  1147.         }
  1148.         writeSeparator = true;
  1149.         (*artNum)++;
  1150.         if (numArticlesToSave == 1) {
  1151.             strcpy(msg, statusMsg);
  1152.         } else {
  1153.             sprintf(msg, statusMsg, *artNum, numArticlesToSave);
  1154.         }
  1155.         err = DisplayStatusMessage(msg);
  1156.         if (err != noErr) return err;
  1157.         number = (*subjectArray)[index].number;
  1158.         truncateIfAttachedFile = !saveEncodedText;
  1159.         flagReqd = EncodedTextBeginLineIsRequired(subjectArray, index);
  1160.         err = CopyArticleToFile(groupName, number, nil, "ARTICLE", refNum, 
  1161.             truncateIfAttachedFile, flagReqd, &fileKind);
  1162.         if (err != noErr) return err;
  1163.         if ((*subjectArray)[index].collapsed) {
  1164.             index = (*subjectArray)[index].nextInThread;
  1165.             if (index == -1) break;
  1166.         } else {
  1167.             break;
  1168.         }
  1169.     }
  1170.     MarkSubjectCellRead(wind, theCell);
  1171.     
  1172.     return noErr;
  1173. }
  1174.  
  1175.  
  1176.  
  1177. /*----------------------------------------------------------------------------
  1178.     SaveSelectedArticlesToFile 
  1179.     
  1180.     Save selected articles to a file.
  1181.             
  1182.     Entry:    wind = pointer to subject window.
  1183.             fSpec = pointer to file spec.
  1184.             scriptTag = script code.
  1185.             append = true to append to end of file.
  1186.             saveEncodedText = true to save encoded text.
  1187.             saveThreadsToSeparateFiles = true to save threads to
  1188.                 separate files.
  1189.     
  1190.     Exit:    function result = error code.
  1191. ----------------------------------------------------------------------------*/
  1192.  
  1193. static OSErr SaveSelectedArticlesToFile (WindowPtr wind, FSSpec *fSpec, 
  1194.     ScriptCode scriptTag, Boolean append, Boolean saveEncodedText,
  1195.     Boolean saveThreadsToSeparateFiles)
  1196. {
  1197.     TWindow **info;
  1198.     ListHandle theList;
  1199.     TSubject **subjectArray;
  1200.     short cellDataLen, index;
  1201.     short numCells;
  1202.     Cell theCell;
  1203.     short *p, *pBegin, *pEnd, numArticlesToSave = 0, artNum = 0;
  1204.     OSErr err = noErr;
  1205.     CStr255 statusMsg;
  1206.     short refNum = 0;
  1207.     Boolean empty, writeSeparator;
  1208.     short threadHeadIndex, prevThreadHeadIndex;
  1209.     
  1210.     MyICReadSharedPrefs(kICeditorHelper);
  1211.     
  1212.     info = (TWindow**) GetWRefCon(wind);
  1213.     theList = (**info).theList;
  1214.     subjectArray = (**info).subjectArray;
  1215.     numCells = (**theList).dataBounds.bottom;
  1216.     
  1217.     SetPt(&theCell, 0, 0);
  1218.     while (theCell.v < numCells) {
  1219.         pBegin = (**theList).cellArray;
  1220.         pEnd = pBegin + numCells;
  1221.         p = pBegin + theCell.v;
  1222.         while (*p >= 0 && p < pEnd) p++;
  1223.         theCell.v = p - pBegin;
  1224.         if (p < pEnd) {
  1225.             cellDataLen = 2;
  1226.             LGetCell(&index, &cellDataLen, theCell, theList);
  1227.             if ((*subjectArray)[index].collapsed) {
  1228.                 numArticlesToSave += (*subjectArray)[index].threadLength;
  1229.             } else {
  1230.                 numArticlesToSave++;
  1231.             }
  1232.         }
  1233.         theCell.v++;
  1234.     }
  1235.     
  1236.     if (numArticlesToSave == 0) {
  1237.         return noErr;
  1238.     } else if (numArticlesToSave == 1) {
  1239.         GetCString(kStrGettingAndSavingArticle, statusMsg);
  1240.     } else {
  1241.         GetCString(kStrGettingAndSavingArticleNofM, statusMsg);
  1242.     }
  1243.     
  1244.     if (!saveThreadsToSeparateFiles) {
  1245.         err = OpenDataForkWriteCreateIfMissing(fSpec, gPrefs.savedArtCreator, 'TEXT',
  1246.             scriptTag, append, &refNum, &empty);
  1247.         if (err != noErr) return err;
  1248.         writeSeparator = !empty;
  1249.     }
  1250.     
  1251.     SetPt(&theCell, 0, 0);
  1252.     prevThreadHeadIndex = -1;
  1253.     while (theCell.v < numCells) {
  1254.         pBegin = (**theList).cellArray;
  1255.         pEnd = pBegin + numCells;
  1256.         p = pBegin + theCell.v;
  1257.         while (*p >= 0 && p < pEnd) p++;
  1258.         theCell.v = p - pBegin;
  1259.         if (p < pEnd) {
  1260.             if (saveThreadsToSeparateFiles) {
  1261.                 cellDataLen = 2;
  1262.                 LGetCell(&index, &cellDataLen, theCell, theList);
  1263.                 threadHeadIndex = (*subjectArray)[index].threadHeadIndex;
  1264.                 if (threadHeadIndex != prevThreadHeadIndex) {
  1265.                     if (refNum != 0) {
  1266.                         MyFSClose(refNum, GiveTime);
  1267.                         refNum = 0;
  1268.                         err = FormFileName(wind, theCell, fSpec->name);
  1269.                         if (err != noErr) goto exit;
  1270.                         err = FileOrFolderExists(fSpec);
  1271.                         if (err != noErr && err != fnfErr) goto exit;
  1272.                         if (err == noErr && !append) {
  1273.                             err = MakeFileNameUnique(fSpec, nil);
  1274.                             if (err != noErr) goto exit;
  1275.                         }
  1276.                     }
  1277.                     err = OpenDataForkWriteCreateIfMissing(fSpec, 
  1278.                         gPrefs.savedArtCreator, 'TEXT',
  1279.                         scriptTag, append, &refNum, &empty);
  1280.                     if (err != noErr) goto exit;
  1281.                     writeSeparator = !empty;
  1282.                     prevThreadHeadIndex = threadHeadIndex;
  1283.                 }
  1284.             }
  1285.             err = SaveOneCellToFile(wind, theCell, refNum, statusMsg, &artNum, 
  1286.                 numArticlesToSave, writeSeparator, saveEncodedText);
  1287.             if (err != noErr) goto exit;
  1288.             writeSeparator = true;
  1289.         }
  1290.         theCell.v++;
  1291.     }
  1292.     
  1293.     DoMarkCommand(wind, true);
  1294.  
  1295. exit:
  1296.  
  1297.     if (refNum != 0) MyFSClose(refNum, GiveTime);
  1298.     return err;
  1299. }
  1300.  
  1301.  
  1302.  
  1303. /*----------------------------------------------------------------------------
  1304.     PrintOneArticle 
  1305.     
  1306.     Print a single article.
  1307.             
  1308.     Entry:    groupName = group name.
  1309.             number = article number.
  1310.             artNum = pointer to article ordinal within sequence of articles
  1311.                 being printed (1,2,3,...)
  1312.             numArts = number of articles being printed.
  1313.             requireEncodedTextBeginLIne = true if BinHex or uuencode text
  1314.                 must include special "begin" flag line.
  1315.     
  1316.     Exit:    function result = error code.
  1317. ----------------------------------------------------------------------------*/
  1318.  
  1319. static OSErr PrintOneArticle (CStr255 groupName, long number,
  1320.     short *artNum, short numArts, Boolean flagReqd)
  1321. {
  1322.     OSErr err = noErr;
  1323.     Handle text = nil;
  1324.     long textLength;
  1325.     Boolean attachedFile;
  1326.     CStr255 subject, from, str;
  1327.     CStr255 msg, fmt;
  1328.  
  1329.     if (numArts == 1) {
  1330.         GetCString(kStrGettingAndPrinting, msg);
  1331.     } else {
  1332.         GetCString(kStrGettingAndPrintingNofM, fmt);
  1333.         sprintf(msg, fmt, *artNum, numArts);
  1334.     }
  1335.     err = DisplayStatusMessage(msg);
  1336.     if (err != noErr) return err;
  1337.     err = GetArticle(nil, 0, groupName, number, nil, "ARTICLE", true, 
  1338.         flagReqd, &text, &textLength, &attachedFile);
  1339.     if (err != noErr) return err;
  1340.     FindHeaderCString(text, "Subject", subject, sizeof(subject));
  1341.     if (FindHeaderCString(text, "From", from, sizeof(from))) {
  1342.         FormatAuthorName(from);
  1343.         sprintf(str, "%.40s, %.100s", from, subject);
  1344.     } else {
  1345.         strcpy(str, subject);
  1346.     }
  1347.     err = PrintText(text, 0, textLength, str);
  1348.     MyDisposeHandle(text);
  1349.     (*artNum)++;
  1350.     return err;
  1351. }
  1352.  
  1353.  
  1354.  
  1355. /*----------------------------------------------------------------------------
  1356.     PrintOneCell 
  1357.     
  1358.     Print a single cell.
  1359.             
  1360.     Entry:    wind = pointer to subject window.
  1361.             theCell = cell to print.
  1362.             artNum = pointer to article ordinal within sequence of articles
  1363.                 being printed (1,2,3,...)
  1364.             numArts = number of articles being printed.
  1365.     
  1366.     Exit:    function result = error code.
  1367. ----------------------------------------------------------------------------*/
  1368.  
  1369. static OSErr PrintOneCell (WindowPtr wind, Cell theCell, 
  1370.     short *artNum, short numArts)
  1371. {
  1372.     TWindow **info;
  1373.     ListHandle theList;
  1374.     TSubject **subjectArray;
  1375.     short index, cellDataLen;
  1376.     long number;
  1377.     CStr255 groupName;
  1378.     OSErr err = noErr;
  1379.     Boolean flagReqd;
  1380.     
  1381.     info = (TWindow**)GetWRefCon(wind);
  1382.     theList = (**info).theList;
  1383.     subjectArray = (**info).subjectArray;
  1384.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  1385.     cellDataLen = 2;
  1386.     LGetCell(&index, &cellDataLen, theCell, theList);
  1387.     
  1388.     while (true) {
  1389.         number = (*subjectArray)[index].number;
  1390.         flagReqd = EncodedTextBeginLineIsRequired(subjectArray, index);
  1391.         err = PrintOneArticle(groupName, number, artNum, numArts, flagReqd);
  1392.         if (err != noErr) return err;
  1393.         if ((*subjectArray)[index].collapsed) {
  1394.             index = (*subjectArray)[index].nextInThread;
  1395.             if (index == -1) break;
  1396.         } else {
  1397.             break;
  1398.         }
  1399.     }
  1400.     
  1401.     MarkSubjectCellRead(wind, theCell);
  1402.     
  1403.     return noErr;
  1404. }
  1405.  
  1406.  
  1407.  
  1408. /*----------------------------------------------------------------------------
  1409.     DragSubjectsSendProc 
  1410.     
  1411.     The Drag Manager send proc for dragging subjects to the Finder.
  1412.             
  1413.     Entry:    theType = flavor type = 'SPEC'.
  1414.             dragSendRefCon = nil.
  1415.             theItemRef = item reference.
  1416.             theDragRef = drag reference.
  1417.     
  1418.     Exit:    function result = error code.
  1419.             gDragTheFile = file spec to be used to save the articles.
  1420. ----------------------------------------------------------------------------*/
  1421.  
  1422. static pascal OSErr DragSubjectsSendProc (FlavorType theType,
  1423.     void *dragSendRefCon, ItemReference theItemRef, DragReference theDragRef)
  1424. {
  1425.     AEDesc dropLocation;
  1426.     OSErr err = noErr;
  1427.     Cell theCell = {0, 0};
  1428.     
  1429.     if (DragTargetWasTrash(theDragRef)) return userCanceledErr;
  1430.     
  1431.     MyICReadSharedPrefs(kICeditorHelper);
  1432.  
  1433.     err = GetDropLocation(theDragRef, &dropLocation);
  1434.     if (err != noErr) return err;
  1435.     
  1436.     err = GetDropLocationDirectory(&dropLocation, &gDragTheFile.vRefNum, 
  1437.         &gDragTheFile.parID);
  1438.     if (err != noErr) goto exit;
  1439.     
  1440.     err = FormFileName(gDragSrcWindow, theCell, gDragTheFile.name);
  1441.     if (err != noErr) goto exit;
  1442.     
  1443.     err = MakeFileNameUnique(&gDragTheFile, nil);
  1444.     if (err != noErr) goto exit;
  1445.  
  1446.     err = FSpCreate(&gDragTheFile, gPrefs.savedArtCreator, 'TEXT', smSystemScript);
  1447.     if (err != noErr) goto exit;
  1448.     
  1449.     err = SetDragItemFlavorData(theDragRef, theItemRef, 'SPEC', 
  1450.         &gDragTheFile, sizeof(FSSpec), 0);
  1451.  
  1452. exit:
  1453.  
  1454.     AEDisposeDesc(&dropLocation);
  1455.     return err;
  1456. }
  1457.  
  1458.  
  1459.  
  1460. /*----------------------------------------------------------------------------
  1461.     SubjectListClickLoop
  1462.     
  1463.     The click loop routine for subject lists when we have the Drag Manager.
  1464.     
  1465.     Exit:    function result = true if mouse button still down, false
  1466.                 if mouse button released.
  1467. ----------------------------------------------------------------------------*/
  1468.  
  1469. static pascal SubjectListClickLoop (void)
  1470. {    
  1471.     TWindow **info;
  1472.     ListHandle theList;
  1473.     OSErr err = noErr;
  1474.     DragReference dragRef;
  1475.     Boolean haveDragRef = false;
  1476.     RgnHandle dragRgn = nil;
  1477.     PromiseHFSFlavor promise;
  1478.     
  1479.     info = (TWindow**)GetWRefCon(gDragSrcWindow);
  1480.     theList = (**info).theList;
  1481.  
  1482.     if (gFirstListClickCall) {
  1483.         gFirstListClickCall = false;
  1484.         return true;
  1485.     }
  1486.     
  1487.     if (WaitMouseMoved(gCurEvent.where)) {
  1488.         gDidDrag = true;
  1489.         err = NewDrag(&dragRef);
  1490.         if (err != noErr) goto exit;
  1491.         MyICReadSharedPrefs(kICeditorHelper);
  1492.         haveDragRef = true;
  1493.         promise.fileType = 'TEXT';
  1494.         promise.fileCreator = gPrefs.savedArtCreator;
  1495.         promise.fdFlags = 0;
  1496.         promise.promisedFlavor = 'SPEC';
  1497.         err = AddDragItemFlavor(dragRef, 1, flavorTypePromiseHFS, &promise, 
  1498.             sizeof(PromiseHFSFlavor), 0);
  1499.         if (err != noErr) goto exit;
  1500.         err = AddDragItemFlavor(dragRef, 1, 'SPEC', nil, 0, 0);
  1501.         if (err != noErr) goto exit;
  1502.         err = SetDragSendProc(dragRef, gDragSubjectsSendProcUPP, nil);
  1503.         if (err != noErr) goto exit;
  1504.         BuildListSelectedCellsDragRegion(theList, &dragRgn);
  1505.         err = TrackDrag(dragRef, &gCurEvent, dragRgn);
  1506.         if (err != noErr) goto exit;
  1507.         DisposeRgn(dragRgn);
  1508.         DisposeDrag(dragRef);
  1509.         gDraggedToFinder = true;
  1510.     }
  1511.     
  1512.     return false;
  1513.     
  1514. exit:
  1515.  
  1516.     if (dragRgn != nil) DisposeRgn(dragRgn);
  1517.     if (haveDragRef) DisposeDrag(dragRef);
  1518.     gClickLoopErr = err;
  1519.     return false;
  1520. }
  1521.  
  1522.  
  1523.  
  1524. /*----------------------------------------------------------------------------
  1525.     ExtractBinariesManuallyDlgLabelUserItem
  1526.     
  1527.     A user item procedure to draw the label in the extract binaries
  1528.     manually dialog.
  1529.     
  1530.     Entry:    dlg = pointer to dialog.
  1531.             item = item number.
  1532. ----------------------------------------------------------------------------*/
  1533.  
  1534. static pascal void ExtractBinariesManuallyDlgLabelUserItem (DialogPtr dlg, short item)
  1535. {
  1536.     Str255 label;
  1537.     Handle itemHandle;
  1538.     short itemType;
  1539.     Rect box;
  1540.     TextStyle savedStyle;
  1541.  
  1542.     GetDialogItem(dlg, item, &itemType, &itemHandle, &box);
  1543.     GetPString(kStrExtractBinariesManuallyDlgLabel, label);
  1544.     GetPortTextStyle(&savedStyle);
  1545.     TextFont(systemFont);
  1546.     TextSize(12);
  1547.     TETextBox(label+1, label[0], &box, teForceLeft);
  1548.     SetPortTextStyle(&savedStyle);
  1549. }
  1550.  
  1551.  
  1552.  
  1553. /*----------------------------------------------------------------------------
  1554.     ExtractBinariesManuallyDlgListUserItem
  1555.     
  1556.     A user item procedure to draw the list in the extract binaries
  1557.     manually dialog.
  1558.     
  1559.     Entry:    dlg = pointer to dialog.
  1560.             item = item number.
  1561. ----------------------------------------------------------------------------*/
  1562.  
  1563. static pascal void ExtractBinariesManuallyDlgListUserItem (DialogPtr dlg, short item)
  1564. {
  1565.     Handle itemHandle;
  1566.     short itemType;
  1567.     Rect box;
  1568.  
  1569.     GetDialogItem(dlg, item, &itemType, &itemHandle, &box);
  1570.     FrameRect(&box);
  1571.     LUpdate(dlg->visRgn, gExtractBinariesManuallyList);
  1572. }
  1573.  
  1574.  
  1575.  
  1576. /*----------------------------------------------------------------------------
  1577.     ExtractBinariesManuallyHandleTracking 
  1578.     
  1579.     Drag Manager tracking handler for the extract binaries manually
  1580.     dialog.
  1581.     
  1582.     Entry:    message = tracking message from Drag Manager.
  1583.             wind = pointer to dialog.
  1584.             handlerRefCon = reference constant (nil).
  1585.             theDrag = drag reference.
  1586.             
  1587.     Exit:    function result = error code.
  1588. ----------------------------------------------------------------------------*/
  1589.  
  1590. static pascal OSErr ExtractBinariesManuallyHandleTracking (DragTrackingMessage message,
  1591.     WindowPtr wind, void *handlerRefCon, DragReference theDrag)
  1592. {
  1593.     Rect rView, visible, dataBounds, contentRect;
  1594.     Point where;
  1595.     Boolean inContentRect;
  1596.     short scrollDelta, newDestRow;
  1597.     static short prevScrollDelta;
  1598.     static long scrollTickCount;
  1599.     
  1600.     rView = (**gExtractBinariesManuallyList).rView;
  1601.     visible = (**gExtractBinariesManuallyList).visible;
  1602.     dataBounds = (**gExtractBinariesManuallyList).dataBounds;
  1603.     contentRect = rView;
  1604.     contentRect.top = 0;
  1605.     contentRect.bottom = wind->portRect.bottom;
  1606.  
  1607.     switch (message) {
  1608.     
  1609.         case dragTrackingEnterHandler:
  1610.         
  1611.             break;
  1612.             
  1613.         case dragTrackingEnterWindow:
  1614.         
  1615.             gDragDestRow = -1;
  1616.             prevScrollDelta = 0;
  1617.             break;
  1618.             
  1619.         case dragTrackingInWindow:
  1620.         
  1621.             GetDragMouse(theDrag, &where, nil);
  1622.             GlobalToLocal(&where);
  1623.             inContentRect = PtInRect(where, &contentRect);
  1624.             if (inContentRect) {
  1625.                 scrollDelta = 0;
  1626.                 if (gDragDestRow >= 0) {
  1627.                     if (where.v < rView.top && visible.top > 0) {
  1628.                         scrollDelta = -1;
  1629.                     } else if (where.v > rView.bottom && 
  1630.                         visible.bottom < dataBounds.bottom) 
  1631.                     {
  1632.                         scrollDelta = +1;
  1633.                     }
  1634.                 }
  1635.                 if (scrollDelta == prevScrollDelta) {
  1636.                     if (scrollDelta != 0 && TickCount() - scrollTickCount < 10) 
  1637.                         scrollDelta = 0;
  1638.                 } else {
  1639.                     prevScrollDelta = scrollDelta;
  1640.                     scrollDelta = 0;
  1641.                     scrollTickCount = TickCount();
  1642.                 } 
  1643.                 if (scrollDelta != 0) {
  1644.                     DrawListDividingLine(gExtractBinariesManuallyList, gDragDestRow);
  1645.                     gDragDestRow = -1;
  1646.                     MyLScroll(scrollDelta, gExtractBinariesManuallyList);
  1647.                 }
  1648.                 newDestRow = ListDestinationRow(gExtractBinariesManuallyList, where);
  1649.                 if (newDestRow != gDragDestRow) {
  1650.                     if (gDragDestRow >= 0) 
  1651.                         DrawListDividingLine(gExtractBinariesManuallyList, gDragDestRow);
  1652.                     gDragDestRow = newDestRow;
  1653.                     DrawListDividingLine(gExtractBinariesManuallyList, gDragDestRow);
  1654.                 }
  1655.             } else {
  1656.                 if (gDragDestRow >= 0) {
  1657.                     DrawListDividingLine(gExtractBinariesManuallyList, gDragDestRow);
  1658.                     gDragDestRow = -1;
  1659.                 }
  1660.                 prevScrollDelta = 0;
  1661.             }
  1662.             break;
  1663.             
  1664.         case dragTrackingLeaveWindow:
  1665.         
  1666.             if (gDragDestRow >= 0) 
  1667.                 DrawListDividingLine(gExtractBinariesManuallyList, gDragDestRow);
  1668.             gDragDestRow = -1;
  1669.             prevScrollDelta = 0;
  1670.             break;
  1671.     
  1672.         case dragTrackingLeaveHandler:
  1673.         
  1674.             break;
  1675.             
  1676.     }
  1677.  
  1678.     return noErr;
  1679. }
  1680.  
  1681.  
  1682.  
  1683. /*----------------------------------------------------------------------------
  1684.     ExtractBinariesManuallyHandleReceive 
  1685.     
  1686.     Drag Manager receive handler for the extract binaries manually
  1687.     dialog.
  1688.     
  1689.     Entry:    wind = pointer to dialog.
  1690.             handlerRefCon = reference constant (nil).
  1691.             theDrag = drag reference.
  1692.             
  1693.     Exit:    function result = error code.
  1694.     
  1695.     Note: No user interaction or network transactions are permitted in
  1696.     this function.
  1697. ----------------------------------------------------------------------------*/
  1698.  
  1699. static pascal OSErr ExtractBinariesManuallyHandleReceive (WindowPtr wind, 
  1700.     void *handlerRefCon, DragReference theDrag)
  1701. {
  1702.     Boolean changed;
  1703.  
  1704.     if (gDragDestRow == -1) return dragNotAcceptedErr;
  1705.     MoveSelectedListCells(gExtractBinariesManuallyList, gDragDestRow, &changed);
  1706.     return noErr;
  1707. }
  1708.  
  1709.  
  1710.  
  1711. /*----------------------------------------------------------------------------
  1712.     ExtractBinariesManuallyClickLoop
  1713.     
  1714.     The click loop routine for the extract binaries manually dialog list 
  1715.     when we have the Drag Manager. It initiates subject drags.
  1716.     
  1717.     Exit:    function result = true if mouse button still down, false
  1718.                 if mouse button released.
  1719. ----------------------------------------------------------------------------*/
  1720.  
  1721. static Boolean ExtractBinariesManuallyClickLoop (void)
  1722. {    
  1723.     OSErr err = noErr;
  1724.     DragReference dragRef;
  1725.     Boolean haveDragRef = false;
  1726.     RgnHandle dragRgn = nil;
  1727.  
  1728.     if (gFirstListClickCall) {
  1729.         gFirstListClickCall = false;
  1730.         return true;
  1731.     }
  1732.     
  1733.     if (WaitMouseMoved(gCurEvent.where)) {
  1734.         err = NewDrag(&dragRef);
  1735.         if (err != noErr) goto exit;
  1736.         haveDragRef = true;
  1737.         err = AddDragItemFlavor(dragRef, 1, kNewsWatcherSignature, 
  1738.             nil, 0, flavorSenderOnly);
  1739.         if (err != noErr) goto exit;
  1740.         BuildListSelectedCellsDragRegion(gExtractBinariesManuallyList, &dragRgn);
  1741.         gDragDestRow = -1;
  1742.         err = TrackDrag(dragRef, &gCurEvent, dragRgn);
  1743.         if (err != noErr) goto exit;
  1744.         DisposeRgn(dragRgn);
  1745.         DisposeDrag(dragRef);
  1746.     }
  1747.     
  1748.     return false;
  1749.     
  1750. exit:
  1751.  
  1752.     if (dragRgn != nil) DisposeRgn(dragRgn);
  1753.     if (haveDragRef) DisposeDrag(dragRef);
  1754.     gClickLoopErr = err;
  1755.     return false;
  1756. }
  1757.  
  1758.  
  1759.  
  1760. /*----------------------------------------------------------------------------
  1761.     ExtractBinariesManuallyDialogFilter 
  1762.     
  1763.     Extract binaries manually dialog filter.
  1764.         
  1765.     Entry:    dlg = pointer to dialog.
  1766.             ev = pointer to event record.
  1767.             
  1768.     Exit:    function result = true if event handled and item hit.
  1769.             *itemHit = item number of item hit.
  1770.             ev = pointer to possibly modified event record.
  1771. ----------------------------------------------------------------------------*/
  1772.  
  1773. static pascal Boolean ExtractBinariesManuallyDialogFilter (DialogPtr dlg, 
  1774.     EventRecord *ev, short *itemHit)
  1775. {
  1776.     short itemType;
  1777.     Handle itemHandle;
  1778.     Rect box;
  1779.     Point where;
  1780.     Boolean shift, command;
  1781.  
  1782.     if (ev->what == mouseDown) {
  1783.         GetDialogItem(dlg, kExtractBinariesManuallyListItem, &itemType, &itemHandle, &box);
  1784.         where = ev->where;
  1785.         GlobalToLocal(&where);
  1786.         if (PtInRect(where, &box)) {
  1787.             shift = (ev->modifiers & shiftKey) != 0;
  1788.             command = (ev->modifiers & cmdKey) != 0;
  1789.             if (!shift && !command) {
  1790.                 if (gHaveDragMgr) {
  1791.                     gFirstListClickCall = true;
  1792.                     gClickLoopErr = noErr;
  1793.                     gDragSrcWindow = dlg;
  1794.                     gCurEvent = *ev;
  1795.                     (**gExtractBinariesManuallyList).lClickLoop =
  1796.                         gExtractBinariesManuallyClickLoopUPP;
  1797.                     MyLClick(where, ev->modifiers, gExtractBinariesManuallyList);
  1798.                 } else {
  1799.                     OldBeginListClick(gExtractBinariesManuallyList);
  1800.                     (**gExtractBinariesManuallyList).lClickLoop =
  1801.                         gOldListClickLoopUPP;
  1802.                     MyLClick(where, ev->modifiers, gExtractBinariesManuallyList);
  1803.                     OldEndListClick();
  1804.                 }
  1805.             } else {
  1806.                 (**gExtractBinariesManuallyList).lClickLoop = nil;
  1807.                 MyLClick(where, ev->modifiers, gExtractBinariesManuallyList);
  1808.             }
  1809.             return false;
  1810.         }
  1811.     }
  1812.     return DialogFilter(dlg, ev, itemHit);
  1813. }
  1814.  
  1815.  
  1816.  
  1817. /*----------------------------------------------------------------------------
  1818.     Find 
  1819.     
  1820.     Search a subject window for a pattern.
  1821.             
  1822.     Entry:    wind = pointer to subject window.
  1823.             theCell = first cell to search.
  1824.             again = true if "Find Again", in which case the search starts
  1825.                 at the article *following* the first cell to search.
  1826.             gFindPattern = pattern.
  1827.     
  1828.     Exit:    function result = error code.
  1829. ----------------------------------------------------------------------------*/
  1830.  
  1831. static OSErr Find (WindowPtr wind, Cell theCell, Boolean again)
  1832. {
  1833.     TWindow **info;
  1834.     TSubject **subjectArray;
  1835.     TSubject theSubject;
  1836.     ListHandle theList;
  1837.     Handle strings;
  1838.     short index, cellDataLen, numCells, autoExpandedThread;
  1839.     unsigned long tickLongTime;
  1840.     Boolean longTime = false;
  1841.     OSErr err = noErr;
  1842.     char state;
  1843.     CStr255 str;
  1844.     Boolean authorMatch, subjectMatch;
  1845.     Cell autoExpandedCell;
  1846.     
  1847.     info = (TWindow**)GetWRefCon(wind);
  1848.     subjectArray = (**info).subjectArray;
  1849.     theList = (**info).theList;
  1850.     strings = (**info).strings;
  1851.     numCells = (**theList).dataBounds.bottom;
  1852.     tickLongTime = TickCount() + 30;
  1853.     
  1854.     state = MyHGetState(strings);
  1855.     MyHLock(strings);
  1856.     
  1857.     while (theCell.v < numCells) {
  1858.     
  1859.         cellDataLen = 2;
  1860.         LGetCell(&index, &cellDataLen, theCell, theList);
  1861.         theSubject = (*subjectArray)[index];
  1862.         
  1863.         if (again) {
  1864.             if (theSubject.collapsed) {
  1865.                 index = theSubject.nextInThread;
  1866.             } else {
  1867.                 index = -1;
  1868.             }
  1869.             again = false;
  1870.         }
  1871.         
  1872.         while (index != -1) {
  1873.         
  1874.             theSubject = (*subjectArray)[index];
  1875.             
  1876.             if (theSubject.authorOffset == -1) {
  1877.                 authorMatch = false;
  1878.             } else {
  1879.                 strcpy(str, *strings + theSubject.authorOffset);
  1880.                 FormatAuthorName(str);
  1881.                 authorMatch = MyIsASubstring(str, gFindPattern);
  1882.             }
  1883.             
  1884.             subjectMatch = theSubject.threadOrdinal == 1 &&
  1885.                 MyIsASubstring(*strings + theSubject.subjectOffset, gFindPattern);
  1886.             
  1887.             if (authorMatch || subjectMatch) {
  1888.                 MyHSetState(strings, state);
  1889.                 autoExpandedThread = (**info).autoExpandedThread;
  1890.                 if (autoExpandedThread != -1 && 
  1891.                     autoExpandedThread != theSubject.threadHeadIndex &&
  1892.                     !(*subjectArray)[autoExpandedThread].collapsed &&
  1893.                     FindParentCell(wind, autoExpandedThread, &autoExpandedCell))
  1894.                 {
  1895.                     ExpandCollapseThread(wind, autoExpandedCell);
  1896.                     (**info).autoExpandedThread = -1;
  1897.                     if (autoExpandedCell.v < theCell.v)
  1898.                         theCell.v -= (*subjectArray)[autoExpandedThread].threadLength - 1;
  1899.                     err = RezoomSubjectWindow(wind, false);
  1900.                     if (err != noErr) return err;
  1901.                 }
  1902.                 if (theSubject.collapsed && theSubject.threadOrdinal > 1) {
  1903.                     ExpandCollapseThread(wind, theCell);
  1904.                     (**info).autoExpandedThread = theSubject.threadHeadIndex;
  1905.                     theCell.v += theSubject.threadOrdinal - 1;
  1906.                     err = RezoomSubjectWindow(wind, true);
  1907.                     if (err != noErr) return err;
  1908.                 }
  1909.                 SelectSingleListItem(theList, theCell);
  1910.                 MyLScrollCenter(theCell, theList);
  1911.                 return noErr;
  1912.             }
  1913.             
  1914.             if (!theSubject.collapsed) break;
  1915.             index = theSubject.nextInThread;
  1916.         }
  1917.     
  1918.         theCell.v++;
  1919.         if ((theCell.v & 0xf) == 0) {
  1920.             if (!longTime && TickCount() > tickLongTime) longTime = true;
  1921.             if (longTime) {
  1922.                 err = GiveTime(false);
  1923.                 if (err != noErr) {
  1924.                     MyHSetState(strings, state);
  1925.                     return err;
  1926.                 }
  1927.             }
  1928.         }
  1929.     }    
  1930.     
  1931.     MyHSetState(strings, state);
  1932.     SysBeep(0);
  1933.     return noErr;
  1934. }
  1935.  
  1936.  
  1937.  
  1938. /*----------------------------------------------------------------------------
  1939.     DoSave 
  1940.     
  1941.     Handle the "Save" command.
  1942.             
  1943.     Entry:    wind = pointer to subject window.
  1944.             modifiers = modifiers field from event record.
  1945.     
  1946.     Exit:    function result = error code.
  1947. ----------------------------------------------------------------------------*/
  1948.  
  1949. static OSErr DoSave (WindowPtr wind, short modifiers)
  1950. {
  1951.     Str31 fileName;
  1952.     FSSpec fSpec;
  1953.     ScriptCode scriptTag;
  1954.     Boolean append = false;
  1955.     OSErr err = noErr;
  1956.     Boolean saveEncodedText, saveThreadsToSeparateFiles;
  1957.     Cell theCell = {0, 0};
  1958.  
  1959.     saveEncodedText = gPrefs.saveEncodedText;
  1960.     saveThreadsToSeparateFiles = gPrefs.saveThreadsToSeparateFiles;
  1961.     err = FormFileName(wind, theCell, fileName);
  1962.     if (err != noErr) return err;
  1963.     if (gPrefs.savedArtDefaultFolder && (modifiers & optionKey) == 0) {
  1964.         err = CheckArticleFileExists(fileName, &fSpec, &scriptTag, 
  1965.             &append, &saveEncodedText, &saveThreadsToSeparateFiles);
  1966.         if (err != noErr) return err;
  1967.     } else {
  1968.         err = PresentStandardArticleSaveFileDialog(fileName, &fSpec, &scriptTag, 
  1969.             &saveEncodedText, &saveThreadsToSeparateFiles);
  1970.         if (err != noErr) return err;
  1971.     }
  1972.     return SaveSelectedArticlesToFile(wind, &fSpec, scriptTag, append,
  1973.         saveEncodedText, saveThreadsToSeparateFiles);
  1974. }
  1975.  
  1976.  
  1977.  
  1978. /*----------------------------------------------------------------------------
  1979.     DoSaveAs 
  1980.     
  1981.     Handle the "SaveAs" command.
  1982.             
  1983.     Entry:    wind = pointer to subject window.
  1984.     
  1985.     Exit:    function result = error code.
  1986. ----------------------------------------------------------------------------*/
  1987.  
  1988. static OSErr DoSaveAs (WindowPtr wind)
  1989. {
  1990.     Str31 fileName;
  1991.     FSSpec fSpec;
  1992.     ScriptCode scriptTag;
  1993.     OSErr err = noErr;
  1994.     Boolean saveEncodedText, saveThreadsToSeparateFiles;
  1995.     Cell theCell = {0, 0};
  1996.  
  1997.     saveEncodedText = gPrefs.saveEncodedText;
  1998.     saveThreadsToSeparateFiles = gPrefs.saveThreadsToSeparateFiles;
  1999.     FormFileName(wind, theCell, fileName);
  2000.     err = PresentStandardArticleSaveFileDialog(fileName, &fSpec, &scriptTag, 
  2001.         &saveEncodedText, &saveThreadsToSeparateFiles);
  2002.     if (err != noErr) return err;
  2003.     return SaveSelectedArticlesToFile(wind, &fSpec, scriptTag, false,
  2004.         saveEncodedText, saveThreadsToSeparateFiles);
  2005. }
  2006.  
  2007.  
  2008.  
  2009. /*----------------------------------------------------------------------------
  2010.     DoAppend
  2011.     
  2012.     Handle the "Append" command.
  2013.             
  2014.     Entry:    wind = pointer to subject window.
  2015.     
  2016.     Exit:    function result = error code.
  2017. ----------------------------------------------------------------------------*/
  2018.  
  2019. static OSErr DoAppend (WindowPtr wind)
  2020. {
  2021.     FSSpec fSpec;
  2022.     OSErr err = noErr;
  2023.     Boolean saveEncodedText;
  2024.  
  2025.     saveEncodedText = gPrefs.saveEncodedText;
  2026.     err = PresentStandardArticleGetFileDialog(&fSpec, &saveEncodedText);
  2027.     if (err != noErr) return err;
  2028.     return SaveSelectedArticlesToFile(wind, &fSpec, smSystemScript, true,
  2029.         saveEncodedText, false);
  2030. }
  2031.  
  2032.  
  2033.  
  2034. /*----------------------------------------------------------------------------
  2035.     DoPrint
  2036.     
  2037.     Handle the "Print" command.
  2038.             
  2039.     Entry:    wind = pointer to subject window.
  2040.     
  2041.     Exit:    function result = error code.
  2042. ----------------------------------------------------------------------------*/
  2043.  
  2044. static OSErr DoPrint (WindowPtr wind)
  2045. {
  2046.     TWindow **info;
  2047.     ListHandle theList;
  2048.     TSubject **subjectArray;
  2049.     short numCells;
  2050.     Cell theCell;
  2051.     short *p, *pBegin, *pEnd;
  2052.     OSErr err = noErr;
  2053.     short artNum, numArts;
  2054.     short cellDataLen, index;
  2055.     
  2056.     err = StartPrint();
  2057.     if (err != noErr) return err;
  2058.     
  2059.     info = (TWindow**) GetWRefCon(wind);
  2060.     theList = (**info).theList;
  2061.     numCells = (**theList).dataBounds.bottom;
  2062.     subjectArray = (**info).subjectArray;
  2063.     
  2064.     SetPt(&theCell, 0, 0);
  2065.     numArts = 0;
  2066.     while (theCell.v < numCells) {
  2067.         pBegin = (**theList).cellArray;
  2068.         pEnd = pBegin + numCells;
  2069.         p = pBegin + theCell.v;
  2070.         while (*p >= 0 && p < pEnd) p++;
  2071.         theCell.v = p - pBegin;
  2072.         if (p < pEnd) {
  2073.             cellDataLen = 2;
  2074.             LGetCell(&index, &cellDataLen, theCell, theList);
  2075.             if ((*subjectArray)[index].collapsed) {
  2076.                 numArts += (*subjectArray)[index].threadLength;
  2077.             } else {
  2078.                 numArts++;
  2079.             }
  2080.         }
  2081.         theCell.v++;
  2082.     }
  2083.     
  2084.     artNum = 1;
  2085.     SetPt(&theCell, 0, 0);
  2086.     while (theCell.v < numCells) {
  2087.         pBegin = (**theList).cellArray;
  2088.         pEnd = pBegin + numCells;
  2089.         p = pBegin + theCell.v;
  2090.         while (*p >= 0 && p < pEnd) p++;
  2091.         theCell.v = p - pBegin;
  2092.         if (p < pEnd) {
  2093.             err = PrintOneCell(wind, theCell, &artNum, numArts);
  2094.             if (err != noErr) goto exit;
  2095.         }
  2096.         theCell.v++;
  2097.     }
  2098.  
  2099. exit:
  2100.  
  2101.     return err;
  2102. }
  2103.  
  2104.  
  2105.  
  2106. /*----------------------------------------------------------------------------
  2107.     DoSelectAll 
  2108.     
  2109.     Handle the "Select All" command for a subject window.
  2110.             
  2111.     Entry:    wind = pointer to subject window.
  2112. ----------------------------------------------------------------------------*/
  2113.  
  2114. static void DoSelectAll (WindowPtr wind)
  2115. {
  2116.     TWindow **info;
  2117.     
  2118.     info = (TWindow**)GetWRefCon(wind);
  2119.     SelectOrDeselectAllListItems((**info).theList, true);
  2120. }
  2121.  
  2122.  
  2123.  
  2124. /*----------------------------------------------------------------------------
  2125.     DoDeselectAll 
  2126.     
  2127.     Handle the "Deselect All" command for a subject window.
  2128.             
  2129.     Entry:    wind = pointer to subject window.
  2130. ----------------------------------------------------------------------------*/
  2131.  
  2132. static void DoDeselectAll (WindowPtr wind)
  2133. {
  2134.     TWindow **info;
  2135.     
  2136.     info = (TWindow**)GetWRefCon(wind);
  2137.     SelectOrDeselectAllListItems((**info).theList, false);
  2138. }
  2139.  
  2140.  
  2141.  
  2142. /*----------------------------------------------------------------------------
  2143.     DoFind 
  2144.     
  2145.     Handle the "Find" command for a subject window.
  2146.             
  2147.     Entry:    wind = pointer to subject window.
  2148.     
  2149.     Exit:    function result = error code.
  2150. ----------------------------------------------------------------------------*/
  2151.  
  2152. static OSErr DoFind (WindowPtr wind)
  2153. {
  2154.     TWindow **info;
  2155.     ListHandle theList;
  2156.     Cell theCell;
  2157.     OSErr err = noErr;
  2158.     
  2159.     err = DoFindDialog();
  2160.     if (err != noErr) return err;
  2161.     info = (TWindow**)GetWRefCon(wind);
  2162.     theList = (**info).theList;
  2163.     SetPt(&theCell, 0, 0);
  2164.     if (!gPrefs.startFindAtBeginning) LGetSelect(true, &theCell, theList);
  2165.     return Find(wind, theCell, false);
  2166. }
  2167.  
  2168.  
  2169.  
  2170. /*----------------------------------------------------------------------------
  2171.     DoFindAgain
  2172.     
  2173.     Handle the "Find Again" command for a subject window.
  2174.             
  2175.     Entry:    wind = pointer to subject window.
  2176.     
  2177.     Exit:    function result = error code.
  2178. ----------------------------------------------------------------------------*/
  2179.  
  2180. static OSErr DoFindAgain (WindowPtr wind)
  2181. {
  2182.     TWindow **info;
  2183.     ListHandle theList;
  2184.     Cell theCell;
  2185.     
  2186.     info = (TWindow**)GetWRefCon(wind);
  2187.     
  2188.     theList = (**info).theList;
  2189.     SetPt(&theCell, 0, 0);
  2190.     LGetSelect(true, &theCell, theList);
  2191.     return Find(wind, theCell, true);
  2192. }
  2193.  
  2194.  
  2195.  
  2196. /*----------------------------------------------------------------------------
  2197.     DoReply 
  2198.     
  2199.     Handle the "Reply" command.
  2200.             
  2201.     Entry:    wind = pointer to subject window.
  2202.             modifiers = modifiers field from event record.
  2203.             
  2204.     Exit:    function result = error code.
  2205. ----------------------------------------------------------------------------*/
  2206.  
  2207. static OSErr DoReply (WindowPtr wind, short modifiers)
  2208. {
  2209.     return OpenMessageWindowForAllSelectedCells(wind, modifiers, OpenReplyWindow);
  2210. }
  2211.  
  2212.  
  2213.  
  2214. /*----------------------------------------------------------------------------
  2215.     DoForward 
  2216.     
  2217.     Handle the "Forward" command.
  2218.             
  2219.     Entry:    wind = pointer to subject window.
  2220.             modifiers = modifiers field from event record.
  2221.             
  2222.     Exit:    function result = error code.
  2223. ----------------------------------------------------------------------------*/
  2224.  
  2225. static OSErr DoForward (WindowPtr wind, short modifiers)
  2226. {
  2227.     return OpenMessageWindowForAllSelectedCells(wind, modifiers, OpenForwardWindow);
  2228. }
  2229.  
  2230.  
  2231.  
  2232. /*----------------------------------------------------------------------------
  2233.     DoRedirect 
  2234.     
  2235.     Handle the "Redirect" command.
  2236.             
  2237.     Entry:    wind = pointer to subject window.
  2238.             modifiers = modifiers field from event record.
  2239.             
  2240.     Exit:    function result = error code.
  2241. ----------------------------------------------------------------------------*/
  2242.  
  2243. static OSErr DoRedirect (WindowPtr wind, short modifiers)
  2244. {
  2245.     return OpenMessageWindowForAllSelectedCells(wind, modifiers, OpenRedirectWindow);
  2246. }
  2247.  
  2248.  
  2249.  
  2250. /*----------------------------------------------------------------------------
  2251.     DoExtractBinaries 
  2252.     
  2253.     Handle the "Extract Binaries" command for a subject window.
  2254.             
  2255.     Entry:    wind = pointer to subject window.
  2256.             modifiers = modifiers field from event record.
  2257.     
  2258.     Exit:    function result = error code.
  2259. ----------------------------------------------------------------------------*/
  2260.  
  2261. static OSErr DoExtractBinaries (WindowPtr wind, short modifiers)
  2262. {
  2263.     TWindow **info;
  2264.     ListHandle theList;
  2265.     TSubject **subjectArray;
  2266.     short cellDataLen, index;
  2267.     short numCells, partNum;
  2268.     Cell theCell;
  2269.     short *p, *pBegin, *pEnd, numArts = 0, artNum = 0;
  2270.     OSErr err = noErr;
  2271.     short prevThreadHeadIndex = -1, threadHeadIndex;
  2272.     short numArtsWithoutAttachedFiles = 0, numIncomplete = 0, numArtsNotOnServer = 0;
  2273.     Boolean incomplete;
  2274.     TAttachedFileKind fileKind;
  2275.     CStr255 fmt, msg;
  2276.     
  2277.     info = (TWindow**) GetWRefCon(wind);
  2278.     theList = (**info).theList;
  2279.     subjectArray = (**info).subjectArray;
  2280.     numCells = (**theList).dataBounds.bottom;
  2281.     
  2282.     SetPt(&theCell, 0, 0);
  2283.     while (theCell.v < numCells) {
  2284.         pBegin = (**theList).cellArray;
  2285.         pEnd = pBegin + numCells;
  2286.         p = pBegin + theCell.v;
  2287.         while (*p >= 0 && p < pEnd) p++;
  2288.         theCell.v = p - pBegin;
  2289.         if (p < pEnd) {
  2290.             cellDataLen = 2;
  2291.             LGetCell(&index, &cellDataLen, theCell, theList);
  2292.             incomplete = (*subjectArray)[index].incomplete;
  2293.             partNum = (*subjectArray)[index].partNum;
  2294.             threadHeadIndex = (*subjectArray)[index].threadHeadIndex;
  2295.             if (incomplete) {
  2296.                 numIncomplete++;
  2297.             } else if (partNum == 0x7fff) {
  2298.                 numArts++;
  2299.             } else if (threadHeadIndex != prevThreadHeadIndex) {
  2300.                 prevThreadHeadIndex = threadHeadIndex;
  2301.                 numArts++;
  2302.             }
  2303.         }
  2304.         theCell.v++;
  2305.     }
  2306.     
  2307.     if (numArts == 0) {
  2308.         if (numIncomplete > 0) {
  2309.             if (numIncomplete == 1) {
  2310.                 NoteMessageNumber(kStrDecodePartsMissing);
  2311.             } else {
  2312.                 NoteMessageNumber(kStrAllSelectedArtsHaveMissingParts);
  2313.             }
  2314.         }
  2315.         return noErr;
  2316.     }
  2317.     
  2318.     SetPt(&theCell, 0, 0);
  2319.     prevThreadHeadIndex = -1;
  2320.     while (theCell.v < numCells) {
  2321.         pBegin = (**theList).cellArray;
  2322.         pEnd = pBegin + numCells;
  2323.         p = pBegin + theCell.v;
  2324.         while (*p >= 0 && p < pEnd) p++;
  2325.         theCell.v = p - pBegin;
  2326.         if (p < pEnd) {
  2327.             cellDataLen = 2;
  2328.             LGetCell(&index, &cellDataLen, theCell, theList);
  2329.             threadHeadIndex = (*subjectArray)[index].threadHeadIndex;
  2330.             incomplete = (*subjectArray)[index].incomplete;
  2331.             partNum = (*subjectArray)[index].partNum;
  2332.             threadHeadIndex = (*subjectArray)[index].threadHeadIndex;
  2333.             if (!incomplete && (partNum == 0x7fff || threadHeadIndex != prevThreadHeadIndex)) {
  2334.                 artNum++;
  2335.                 err = ExtractBinaries(wind, index, artNum, numArts, modifiers, &fileKind);
  2336.                 if (err != noErr) return err;
  2337.                 if (fileKind == kNoAttachedFile) numArtsWithoutAttachedFiles++;
  2338.                 if (fileKind == kArtNotOnServer) numArtsNotOnServer++;
  2339.                 if (partNum != 0x7fff) prevThreadHeadIndex = threadHeadIndex;
  2340.             }
  2341.         }
  2342.         theCell.v++;
  2343.     }
  2344.     
  2345.     if (numArtsWithoutAttachedFiles > 0) {
  2346.         if (numArts == 1) {
  2347.             NoteMessageNumber(kStrArtHasNoAttachedFile);
  2348.         } else if (numArtsWithoutAttachedFiles == 1) {
  2349.             NoteMessageNumber(kStrOneArtSkippedNoAttachedFile);
  2350.         } else {
  2351.             GetCString(kStrNArtsSkippedNoAttachedFile, fmt);
  2352.             sprintf(msg, fmt, numArtsWithoutAttachedFiles);
  2353.             NoteMessage(msg);
  2354.         }
  2355.     }
  2356.     
  2357.     if (numArtsNotOnServer > 0) {
  2358.         if (numArts == 1) {
  2359.             NoteMessageNumber(kStrArtNotOnServer);
  2360.         } else if (numArtsNotOnServer == 1) {
  2361.             NoteMessageNumber(kStrOneArtSkippedNotOnServer);
  2362.         } else {
  2363.             GetCString(kStrNArtsSkippedNotOnServer, fmt);
  2364.             sprintf(msg, fmt, numArtsNotOnServer);
  2365.             NoteMessage(msg);
  2366.         }
  2367.     }
  2368.     
  2369.     if (numIncomplete == 1) {
  2370.         NoteMessageNumber(kStrOneArtSkippedMissingParts);
  2371.     } else if (numIncomplete > 1) {
  2372.         GetCString(kStrNArtsSkippedMissingParts, fmt);
  2373.         sprintf(msg, fmt, numIncomplete);
  2374.         NoteMessage(msg);
  2375.     }
  2376.  
  2377.     return noErr;
  2378. }
  2379.  
  2380.  
  2381.  
  2382. /*----------------------------------------------------------------------------
  2383.     DoExtractBinariesManually 
  2384.     
  2385.     Handle the "Extract Binaries Manually" command for a subject window.
  2386.             
  2387.     Entry:    wind = pointer to subject window.
  2388.             modifiers = modifiers field from event record.
  2389.     
  2390.     Exit:    function result = error code.
  2391. ----------------------------------------------------------------------------*/
  2392.  
  2393. static OSErr DoExtractBinariesManually (WindowPtr wind, short modifiers)
  2394. {
  2395.     TWindow **info;
  2396.     ListHandle theList;
  2397.     TSubject **subjectArray;
  2398.     short numCells;
  2399.     short cellDataLen, index;
  2400.     short *p, *pBegin, *pEnd, numSelected = 0;
  2401.     Handle strings;
  2402.     OSErr err = noErr;
  2403.     DialogPtr dlg = nil;
  2404.     short item, fontNum, itemType, lineHeight, listHeight;
  2405.     Handle itemHandle;
  2406.     Rect rView;
  2407.     Rect dataBounds = {0, 0, 0, 1};
  2408.     Point cSize = {0, 0};
  2409.     CStr255 subject, str;
  2410.     Cell cella, cellb;
  2411.     long number;
  2412.     long **artList = nil;
  2413.     short artNum;
  2414.     Str31 fileName;
  2415.     FSSpec fSpec;
  2416.     ScriptCode scriptTag;
  2417.     CStr255 statusMsg, statusMsgFormat;
  2418.     CStr255 groupName;
  2419.     TAttachedFileKind fileKind = kNoAttachedFile, partKind;
  2420.     long totalCellDataLength = 0;
  2421.     short refNum = 0;
  2422.     Boolean empty;
  2423.     
  2424.     gExtractBinariesManuallyList = nil;
  2425.     *fSpec.name = 0;
  2426.     
  2427.     if (!MemoryAvailable(40000)) {
  2428.         err = memFullErr;
  2429.         goto exit;
  2430.     }
  2431.     
  2432.     info = (TWindow**) GetWRefCon(wind);
  2433.     theList = (**info).theList;
  2434.     subjectArray = (**info).subjectArray;
  2435.     strings = (**info).strings;
  2436.     numCells = (**theList).dataBounds.bottom;
  2437.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  2438.  
  2439.     err = MyGetNewDialog(kExtractBinariesManuallyDlg, ok, cancel, &dlg);
  2440.     if (err != noErr) return err;
  2441.     DlgSetUserItem(dlg, kExtractBinariesManuallyLabelItem, 
  2442.         gExtractBinariesManuallyDlgLabelUserItemUPP);
  2443.     DlgSetUserItem(dlg, kExtractBinariesManuallyListItem, 
  2444.         gExtractBinariesManuallyDlgListUserItemUPP);
  2445.         
  2446.     if (gHaveDragMgr) {
  2447.         err = InstallTrackingHandler(gExtractBinariesManuallyHandleTrackingUPP,
  2448.             dlg, nil);
  2449.         if (err != noErr) goto exit;
  2450.         err = InstallReceiveHandler(gExtractBinariesManuallyHandleReceiveUPP,
  2451.             dlg, nil);
  2452.         if (err != noErr) goto exit;
  2453.     }
  2454.     
  2455.     SetPort(dlg);
  2456.     
  2457.     GetFontNumber("\pMonaco", &fontNum);
  2458.     TextFont(fontNum);
  2459.     TextSize(9);
  2460.     
  2461.     GetDialogItem(dlg, kExtractBinariesManuallyListItem, &itemType, &itemHandle, &rView);
  2462.     InsetRect(&rView, 1, 1);
  2463.     rView.right -= 15;
  2464.     lineHeight = GetFontLineHeight(dlg);
  2465.     listHeight = rView.bottom - rView.top;
  2466.     listHeight = listHeight/lineHeight*lineHeight;
  2467.     rView.bottom = rView.top + listHeight;
  2468.     gExtractBinariesManuallyList = LNew(&rView, &dataBounds, cSize, 0,
  2469.         dlg, true, false, false, true);
  2470.     (**gExtractBinariesManuallyList).indent.h = 4;
  2471.     (**gExtractBinariesManuallyList).selFlags |= lNoNilHilite;
  2472.     rView.right += 15;
  2473.     InsetRect(&rView, -1, -1);
  2474.     SetDialogItem(dlg, kExtractBinariesManuallyListItem, itemType, itemHandle, &rView);
  2475.         
  2476.     numSelected = NumListItemsSelected(theList);
  2477.     LAddRow(numSelected, 0, gExtractBinariesManuallyList);
  2478.     
  2479.     SetPt(&cella, 0, 0);
  2480.     SetPt(&cellb, 0, 0);
  2481.     while (cella.v < numCells) {
  2482.         pBegin = (**theList).cellArray;
  2483.         pEnd = pBegin + numCells;
  2484.         p = pBegin + cella.v;
  2485.         while (*p >= 0 && p < pEnd) p++;
  2486.         cella.v = p - pBegin;
  2487.         if (p < pEnd) {
  2488.             cellDataLen = 2;
  2489.             LGetCell(&index, &cellDataLen, cella, theList);
  2490.             strcpy(subject, *strings + (*subjectArray)[index].subjectOffset);
  2491.             sprintf(str, "%ld - %.240s", (*subjectArray)[index].number, subject);
  2492.             totalCellDataLength += strlen(str);
  2493.             if (totalCellDataLength > 0x7fff) {
  2494.                 ErrorMessageNumber(kStrExtractBinariesManuallyTooMuchCellData);
  2495.                 goto exit;
  2496.             }
  2497.             LSetCell(str, strlen(str), cellb, gExtractBinariesManuallyList);
  2498.             cellb.v++;
  2499.         }
  2500.         cella.v++;
  2501.     }
  2502.     
  2503.     MyModalDialog(dlg, gExtractBinariesManuallyDialogFilterUPP, &item);
  2504.     
  2505.     if (item == ok) {
  2506.         err = MyNewHandle(numSelected * sizeof(long), &artList);
  2507.         if (err != noErr) goto exit;
  2508.         SetPt(&cellb, 0, 0);
  2509.         while (cellb.v < numSelected) {
  2510.             cellDataLen = 256;
  2511.             LGetCell(str, &cellDataLen, cellb, gExtractBinariesManuallyList);
  2512.             str[cellDataLen] = 0;
  2513.             number = atol(str);
  2514.             (*artList)[cellb.v] = number;
  2515.             cellb.v++;
  2516.         }
  2517.     }
  2518.     
  2519.     LDispose(gExtractBinariesManuallyList);
  2520.     gExtractBinariesManuallyList = nil;
  2521.     
  2522.     if (gHaveDragMgr) {
  2523.         RemoveTrackingHandler(gExtractBinariesManuallyHandleTrackingUPP, dlg);
  2524.         RemoveReceiveHandler(gExtractBinariesManuallyHandleReceiveUPP, dlg);
  2525.     }
  2526.     
  2527.     err = DoClose(dlg);
  2528.     if (err != noErr) return err;
  2529.     dlg = nil;
  2530.     if (item == cancel) return userCanceledErr;
  2531.     
  2532.     GetPString(kStrTempFileName, fileName);
  2533.     
  2534.     if (gPrefs.savedBinDefaultFolder && (modifiers & optionKey) == 0) {
  2535.         err = CheckDownloadFileExists(fileName, &fSpec, &scriptTag);
  2536.         if (err != noErr) goto exit;
  2537.     } else {
  2538.         err = PresentStandardDownloadSaveFileDialog(fileName, &fSpec, &scriptTag);
  2539.         if (err != noErr) goto exit;
  2540.     }
  2541.     
  2542.     GetCString(kStrGettingAndSavingPartNofM, statusMsgFormat);
  2543.     
  2544.     MyICReadSharedPrefs(kICeditorHelper);
  2545.     
  2546.     err = OpenDataForkWriteCreateIfMissing(&fSpec, gPrefs.savedArtCreator, 'TEXT',
  2547.         scriptTag, false, &refNum, &empty);
  2548.     if (err != noErr) return err;
  2549.     
  2550.     for (artNum = 0; artNum < numSelected; artNum++) {
  2551.         sprintf(statusMsg, statusMsgFormat, artNum+1, numSelected);
  2552.         err = DisplayStatusMessage(statusMsg);
  2553.         if (err != noErr) goto exit;
  2554.         number = (*artList)[artNum];
  2555.         err = CopyArticleToFile(groupName, number, nil, "ARTICLE", refNum, 
  2556.             false, false, &partKind);
  2557.         if (err != noErr) goto exit;
  2558.         if (partKind == kArtNotOnServer) {
  2559.             fileKind = kArtNotOnServer;
  2560.             break;
  2561.         } else if (fileKind == kNoAttachedFile) {
  2562.             fileKind = partKind;
  2563.         }
  2564.     }
  2565.     
  2566.     MyFSClose(refNum, GiveTime);
  2567.     
  2568.     MyDisposeHandle(artList);
  2569.     
  2570.     if (fileKind == kNoAttachedFile) {
  2571.         FSpDelete(&fSpec);
  2572.         NoteMessageNumber(kStrArtHasNoAttachedFile);
  2573.     } else if (fileKind == kArtNotOnServer) {
  2574.         FSpDelete(&fSpec);
  2575.         NoteMessageNumber(kStrArtNotOnServer);
  2576.     } else {
  2577.         err = RunDecodeHelperProgram(&fSpec, fileKind);
  2578.         *fSpec.name = 0;
  2579.         if (err != noErr) goto exit;
  2580.         DoMarkCommand(wind, true);
  2581.     }
  2582.     
  2583.     return noErr;
  2584.     
  2585. exit:
  2586.  
  2587.     if (gExtractBinariesManuallyList != nil) LDispose(gExtractBinariesManuallyList);
  2588.     gExtractBinariesManuallyList = nil;
  2589.     if (dlg != nil) {
  2590.         RemoveTrackingHandler(gExtractBinariesManuallyHandleTrackingUPP, dlg);
  2591.         RemoveReceiveHandler(gExtractBinariesManuallyHandleReceiveUPP, dlg);
  2592.         DoClose(dlg);
  2593.     }
  2594.     MyDisposeHandle(artList);
  2595.     if (refNum != 0) MyFSClose(refNum, GiveTime);
  2596.     if (*fSpec.name != 0) FSpDelete(&fSpec);
  2597.     return err;
  2598. }
  2599.  
  2600.  
  2601.  
  2602. /*----------------------------------------------------------------------------
  2603.     DoCancelArticle 
  2604.     
  2605.     Handle the "Cancel Article" command for a subject window.
  2606.             
  2607.     Entry:    wind = pointer to subject window.
  2608.     
  2609.     Exit:    function result = error code.
  2610. ----------------------------------------------------------------------------*/
  2611.  
  2612. static OSErr DoCancelArticle (WindowPtr wind)
  2613. {
  2614.     Cell theCell;
  2615.     TWindow **info;
  2616.     ListHandle theList;
  2617.     short *p, *pEnd, *pBegin;
  2618.     short numSelected=0, numCanceled=0, i = 0, numCells;
  2619.     OSErr err = noErr;
  2620.     Boolean canceled;
  2621.     CStr255 msg, fmt;
  2622.     
  2623.     info = (TWindow**) GetWRefCon(wind);
  2624.     theList = (**info).theList;
  2625.     numCells = (**theList).dataBounds.bottom;
  2626.     
  2627.     numSelected = NumListItemsSelected(theList);
  2628.     if (numSelected == 0) return noErr;
  2629.     if (numSelected == 1) {
  2630.         GetCString(kStrCancelingStatusMsg, msg);
  2631.     } else {
  2632.         GetCString(kStrCancelingNofMStatusMsg, fmt);
  2633.     }
  2634.     
  2635.     SetPt(&theCell, 0, 0);
  2636.     while (theCell.v < numCells) {
  2637.         pBegin = (**theList).cellArray;
  2638.         pEnd = pBegin + numCells;
  2639.         p = pBegin + theCell.v;
  2640.         while (*p >= 0 && p < pEnd) p++;
  2641.         theCell.v = p - pBegin;
  2642.         if (p < pEnd) {
  2643.             if (numSelected != 1) {
  2644.                 i++;
  2645.                 sprintf(msg, fmt, i, numSelected);
  2646.             }
  2647.             err = DisplayStatusMessage(msg);
  2648.             if (err != noErr) return err;
  2649.             err = CancelSubjectCell(wind, theCell, msg, &canceled);
  2650.             if (err != noErr) return err;
  2651.             if (canceled) numCanceled++;
  2652.         }
  2653.         theCell.v++;
  2654.     }
  2655.     if (numCanceled < numSelected) {
  2656.         if (numSelected == 1) {
  2657.             ErrorMessageNumber(kStrNotCanceled);
  2658.         } else {
  2659.             if (numCanceled == 0) {
  2660.                 ErrorMessageNumber(kStrNoneCanceled);
  2661.             } else if (numSelected - numCanceled == 1) {
  2662.                 ErrorMessageNumber(kStrOneNotCanceled);
  2663.             } else {
  2664.                 GetCString(kStrNNotCanceled, fmt);
  2665.                 sprintf(msg, fmt, numSelected - numCanceled);
  2666.                 ErrorMessage(msg);
  2667.             }
  2668.         }
  2669.     }
  2670.     return noErr;
  2671. }
  2672.  
  2673.  
  2674.  
  2675. /*----------------------------------------------------------------------------
  2676.     Activate 
  2677.     
  2678.     Handle an activate event for a subject window.
  2679.             
  2680.     Entry:    wind = pointer to subject window.
  2681.             act = true to activate, false to deactivate
  2682. ----------------------------------------------------------------------------*/
  2683.  
  2684. static void Activate (WindowPtr wind, Boolean act)
  2685. {
  2686.     TWindow **info;
  2687.     Rect r;
  2688.     
  2689.     info = (TWindow**)GetWRefCon(wind);
  2690.     MyLActivate(act, (**info).theList);
  2691.     r = wind->portRect;
  2692.     r.top = r.bottom - 15;
  2693.     r.left = r.right - 15;
  2694.     InvalRect(&r);
  2695. }
  2696.  
  2697.  
  2698.  
  2699. /*----------------------------------------------------------------------------
  2700.     Update 
  2701.     
  2702.     Handle an update event for a subject window.
  2703.             
  2704.     Entry:    wind = pointer to subject window.
  2705. ----------------------------------------------------------------------------*/
  2706.  
  2707. static void Update (WindowPtr wind)
  2708. {
  2709.     TWindow **info;
  2710.     short panelHeight, windWidth, windHeight, numSubjects, numSubjectsInList;
  2711.     short numUnread, i;
  2712.     TSubject **subjectArray, *pSubject;    
  2713.     Rect r;
  2714.     FontInfo fontInfo;
  2715.     CStr255 str, fmt;
  2716.  
  2717.     info = (TWindow**)GetWRefCon(wind);
  2718.     panelHeight = (**info).panelHeight;
  2719.     GetFontInfo(&fontInfo);
  2720.     windWidth = wind->portRect.right;
  2721.     windHeight = wind->portRect.bottom;
  2722.     
  2723.     r = wind->portRect;
  2724.     r.top += panelHeight;
  2725.     ClipRect(&r);
  2726.     DrawGrowIcon(wind);
  2727.     ClipRect(&wind->portRect);
  2728.     
  2729.     MoveTo(0, panelHeight-3);
  2730.     LineTo(windWidth, panelHeight-3);
  2731.     MoveTo(0, panelHeight-1);
  2732.     LineTo(windWidth, panelHeight-1);
  2733.  
  2734.     numSubjects = (**info).numSubjects;
  2735.     numSubjectsInList = (**info).numSubjectsInList;
  2736.     numUnread = 0;
  2737.     subjectArray = (**info).subjectArray;
  2738.     for (i = 0, pSubject = *subjectArray; i < numSubjects; i++, pSubject++) {
  2739.         if (!pSubject->read && pSubject->inList) numUnread++;
  2740.     }
  2741.     if (numSubjectsInList == 1) {
  2742.         GetCString(kStrOneArt, fmt);
  2743.         sprintf(str, fmt, numUnread);
  2744.     } else {
  2745.         GetCString(kStrNArts, fmt);
  2746.         sprintf(str, fmt, numSubjectsInList, numUnread);
  2747.     }
  2748.     c2pstr(str);
  2749.     TruncString(windWidth - 20, (StringPtr)str, smTruncEnd);
  2750.     MoveTo(10, fontInfo.ascent + 3);
  2751.     DrawString((StringPtr)str);
  2752.  
  2753.     LUpdate(wind->visRgn, (**info).theList);
  2754.     
  2755.     DrawPadlockIcon(wind);
  2756. }
  2757.  
  2758.  
  2759.  
  2760. /*----------------------------------------------------------------------------
  2761.     Mouse 
  2762.     
  2763.     Handle a mouse down event in the content area of a subject window.
  2764.             
  2765.     Entry:    wind = pointer to subject window.
  2766.             where = location of mouse down in local coords.
  2767.             modifiers = modifiers field from event record.
  2768.             
  2769.     Exit:    function result = error code.
  2770. ----------------------------------------------------------------------------*/
  2771.  
  2772. static OSErr Mouse (WindowPtr wind, Point where, short modifiers)
  2773. {
  2774.     TWindow **info;
  2775.     ListHandle theList;
  2776.     Boolean triangleClicked, shift, command, doubleClick;
  2777.     OSErr err = noErr;
  2778.     Rect rView;
  2779.  
  2780.     info = (TWindow**) GetWRefCon(wind);
  2781.     theList = (**info).theList;
  2782.     
  2783.     shift = (modifiers & shiftKey) != 0;
  2784.     command = (modifiers & cmdKey) != 0;
  2785.     
  2786.     if (where.v < (**info).panelHeight) {
  2787.         SelectOrDeselectAllListItems(theList, false);
  2788.         return noErr;
  2789.     }
  2790.     
  2791.     err = TriangleClick(wind, where, &triangleClicked);
  2792.     if (err != noErr) return err;
  2793.     if (triangleClicked) return noErr;
  2794.     
  2795.     if (HandlePadlockClick(wind, where, &gPrefs.subjectWindPosLocked, 
  2796.         &gPrefs.subjectWindLocn)) return noErr;
  2797.     
  2798.     rView = (**theList).rView;
  2799.     if (where.v <= rView.top || where.v >= rView.bottom) return noErr;
  2800.     if (where.v >= rView.bottom - 1) where.v = rView.bottom - 2;
  2801.     
  2802.     if (gHaveDragMgr && !shift && !command && PtInListCell(where, theList)) {
  2803.     
  2804.         gDragSrcWindow = wind;
  2805.         gFirstListClickCall = true;
  2806.         gClickLoopErr = noErr;
  2807.         gDraggedToFinder = false;
  2808.         (**theList).lClickLoop = gSubjectListClickLoopUPP;
  2809.         gDidDrag = false;
  2810.         doubleClick = MyLClickPossiblyInactive(where, modifiers, theList);
  2811.         KillBalloon();
  2812.         if (doubleClick && !gDidDrag) {
  2813.             err = OpenSelectedCells(wind);
  2814.             if (err != noErr) return err;
  2815.         } else {
  2816.             if (gClickLoopErr != noErr) return gClickLoopErr;
  2817.             if (gDraggedToFinder) {
  2818.                 err = SaveSelectedArticlesToFile(wind, &gDragTheFile, smSystemScript, false, 
  2819.                     gPrefs.saveEncodedText, gPrefs.saveThreadsToSeparateFiles);
  2820.                 if (err != noErr) return err;
  2821.             }
  2822.         }
  2823.     
  2824.     } else {
  2825.     
  2826.         (**theList).lClickLoop = nil;
  2827.         if (MyLClickPossiblyInactive(where, modifiers, theList) && !shift && !command) {
  2828.             err = OpenSelectedCells(wind);
  2829.             if (err != noErr) return err;
  2830.         }
  2831.         KillBalloon();
  2832.     }
  2833.     
  2834.     return noErr;
  2835. }
  2836.  
  2837.  
  2838.  
  2839. /*----------------------------------------------------------------------------
  2840.     Draggable
  2841.     
  2842.     Determine whether a mouse down event is on a draggable object in a 
  2843.     subject window.
  2844.     
  2845.     Entry:    wind = pointer to subject window.
  2846.             where = location of mouse down event, in local coordinates.
  2847.             modifiers = modifiers field from event record.
  2848.             
  2849.     Exit:    function result = true if object is draggable.
  2850. ----------------------------------------------------------------------------*/
  2851.  
  2852. static Boolean Draggable (WindowPtr wind, Point where, short modifiers)
  2853. {
  2854.     TWindow **info;
  2855.     ListHandle theList;
  2856.  
  2857.     if ((modifiers & shiftKey) != 0) return false;
  2858.     if ((modifiers & cmdKey) != 0) return false;
  2859.     info = (TWindow**)GetWRefCon(wind);
  2860.     theList = (**info).theList;
  2861.     return PtInListCell(where, theList);
  2862. }
  2863.  
  2864.  
  2865.  
  2866. /*----------------------------------------------------------------------------
  2867.     Key 
  2868.     
  2869.     Handle a key down event for a subject window.
  2870.             
  2871.     Entry:    wind = pointer to subject window.
  2872.             theChar = ASCII code of key.
  2873.             theKey = keyboard code of key.
  2874.             modifiers = modifiers field from event record.
  2875.             
  2876.     Exit:    function result = error code.
  2877. ----------------------------------------------------------------------------*/
  2878.  
  2879. static OSErr Key (WindowPtr wind, unsigned char theChar, unsigned char theKey, 
  2880.     short modifiers)
  2881. {
  2882.     OSErr err = noErr;
  2883.     Cell scrollIntoView;
  2884.     TWindow **info;
  2885.     ListHandle theList;
  2886.     TKeypadKey keypadKey;
  2887.     Boolean command, isArrow;
  2888.  
  2889.     info = (TWindow**)GetWRefCon(wind);
  2890.     theList = (**info).theList;
  2891.     command = (modifiers & cmdKey) != 0;
  2892.     isArrow = IsArrowKey(theChar);
  2893.     
  2894.     if (command && !isArrow) {
  2895.         SysBeep(0);
  2896.         return noErr;
  2897.     }
  2898.     
  2899.     if (command && (theChar == leftArrow || theChar == rightArrow))
  2900.         return ExpandCollapseKey(wind, theChar);
  2901.  
  2902.     if (gPrefs.keypadShortcuts && IsKeypadKey(theChar, theKey, &keypadKey)) {
  2903.         switch (keypadKey) {
  2904.             case kKeypadClearKey:
  2905.                 return DoMarkOthersRead(wind);
  2906.             case kKeypadEqualKey:
  2907.                 DoSelectAll(wind);
  2908.                 return noErr;
  2909.             case kKeypadSlashKey:
  2910.                 return DoExpandCollapseSelectedThread(wind);
  2911.             case kKeypadStarKey:
  2912.                 return DoClose(wind);
  2913.             case kKeypadMinusKey:
  2914.                 return DoMarkCommand(wind, false);
  2915.             case kKeypadPlusKey:
  2916.                 return DoMarkCommand(wind, true);
  2917.             case kKeypadEnterKey:
  2918.                 return DoNextGroup(wind);
  2919.             case kKeypadPeriodKey:
  2920.                 return DoNextThread(wind);
  2921.             case kKeypad0Key:
  2922.                 return DoNextArticle(wind);
  2923.             case kKeypad1Key:
  2924.                 Scroll(wind, kScrollToEnd);
  2925.                 return noErr;
  2926.             case kKeypad2Key:
  2927.                 Scroll(wind, inDownButton);
  2928.                 return noErr;
  2929.             case kKeypad3Key:
  2930.                 Scroll(wind, inPageDown);
  2931.                 return noErr;
  2932.             case kKeypad5Key:
  2933.                 return DoNextArticle(wind);
  2934.             case kKeypad7Key:
  2935.                 Scroll(wind, kScrollToHome);
  2936.                 return noErr;
  2937.             case kKeypad8Key:
  2938.                 Scroll(wind, inUpButton);
  2939.                 return noErr;
  2940.             case kKeypad9Key:
  2941.                 Scroll(wind, inPageUp);
  2942.                 return noErr;
  2943.             default:
  2944.                 SysBeep(0);
  2945.                 return noErr;
  2946.         }
  2947.     }
  2948.     
  2949.     if (theChar == pageUpKey) {
  2950.         Scroll(wind, inPageUp);
  2951.         return noErr;
  2952.     }
  2953.     if (theChar == pageDownKey) {
  2954.         Scroll(wind, inPageDown);
  2955.         return noErr;
  2956.     }
  2957.     if (theChar == homeKey) {
  2958.         Scroll(wind, kScrollToHome);
  2959.         return noErr;
  2960.     }
  2961.     if (theChar == endKey) {
  2962.         Scroll(wind, kScrollToEnd);
  2963.         return noErr;
  2964.     }
  2965.     if (theChar == returnKey || theChar == enterKey) {
  2966.         return OpenSelectedCells(wind);
  2967.     }
  2968.     
  2969.     if (theChar == upArrow || theChar == downArrow) {
  2970.         ListArrowKey(theChar, modifiers, theList, &gPrevEvent, &scrollIntoView);
  2971.         HandleUpdate(wind);
  2972.         MyLScrollCellIntoView(scrollIntoView, theList);
  2973.         return noErr;
  2974.     }
  2975.     
  2976.     if (gPrefs.keyboardShortcuts) {
  2977.     
  2978.         theChar = tolower(theChar);
  2979.         
  2980.         if (theChar == ' ' || theChar == 'n' || theChar == 'i') {
  2981.             return DoNextArticle(wind);
  2982.         }
  2983.         
  2984.         if (theChar == 't') {
  2985.             return DoNextThread(wind);
  2986.         }
  2987.         
  2988.         if (theChar == 'g' || theChar == 'j') {
  2989.             return DoNextGroup(wind);
  2990.         }
  2991.         
  2992.         if (theChar == 'w') {
  2993.             return DoClose(wind);
  2994.         }
  2995.         
  2996.         if (theChar == 'u') {
  2997.             return DoMarkCommand(wind, false);
  2998.         }
  2999.         
  3000.         if (theChar == 'm') {
  3001.             return DoMarkCommand(wind, true);
  3002.         }
  3003.         
  3004.         if (theChar == 'a') {
  3005.             DoSelectAll(wind);
  3006.             return noErr;
  3007.         }
  3008.         
  3009.         if (theChar == ';') {
  3010.             return DoMarkOthersRead(wind);
  3011.         }
  3012.         
  3013.     }
  3014.     
  3015.     SysBeep(0);
  3016.     return noErr;
  3017. }
  3018.  
  3019.  
  3020.  
  3021. /*----------------------------------------------------------------------------
  3022.     Grow 
  3023.     
  3024.     Handle a mouse down event in the grow box of a subject window.
  3025.     
  3026.     Entry:    wind = pointer to subject window.
  3027.             where = location of mouse down event, in global coordinates.
  3028.             
  3029.     Exit:    function result = error code.
  3030. ----------------------------------------------------------------------------*/
  3031.  
  3032. static OSErr Grow (WindowPtr wind, Point where)
  3033. {
  3034.     Rect sizeRect;
  3035.     long size;
  3036.     short width, height;
  3037.     
  3038.     SetRect(&sizeRect, kMinWindowWidth, MinHeight(wind), 0x7fff, 0x7fff);
  3039.     size = GrowWindow(wind, where, &sizeRect);
  3040.     
  3041.     if (size  != 0) {
  3042.         width = LoWord(size);
  3043.         height = HiWord(size);
  3044.         FixHeight(wind, &height);
  3045.         SizeWindow(wind, width, height, false);
  3046.         ResizeContents(wind);
  3047.     }
  3048.     
  3049.     return noErr;
  3050. }
  3051.  
  3052.  
  3053.  
  3054. /*----------------------------------------------------------------------------
  3055.     Zoom
  3056.     
  3057.     Zoom a subject window.
  3058.     
  3059.     Entry:    wind = pointer to subject window.
  3060.             zoomDir = zoom direction = inZoomIn or inZoomOut.
  3061.             
  3062.     Exit:    function result = error code.
  3063. ----------------------------------------------------------------------------*/
  3064.  
  3065. static OSErr Zoom (WindowPtr wind, short zoomDir)
  3066. {
  3067.     TWindow **info;
  3068.     ListHandle theList;
  3069.     TSubject **subjectArray, *pSubjectArray;
  3070.     Handle strings;
  3071.     short width, height, lineHeight, minHeight, panelHeight, i, len;
  3072.     short subjectWidth, maxSubjectWidth, numCells, numSubjects;
  3073.     char *subject;
  3074.     Rect zoomRect;    
  3075.     WStateData **wState;
  3076.     long longHeight;
  3077.     char state1, state2;
  3078.  
  3079.     wState = (WStateData**)((WindowPeek)wind)->dataHandle;
  3080.     if (zoomDir == inZoomOut) {
  3081.         info = (TWindow**)GetWRefCon(wind);
  3082.         theList = (**info).theList;
  3083.         panelHeight = (**info).panelHeight;
  3084.         subjectArray = (**info).subjectArray;
  3085.         strings = (**info).strings;
  3086.         numCells = (**theList).dataBounds.bottom;
  3087.         lineHeight = (**theList).cellSize.v;
  3088.         maxSubjectWidth = (**info).maxSubjectWidth;
  3089.         numSubjects = (**info).numSubjects;
  3090.         
  3091.         if (maxSubjectWidth == 0) {
  3092.             state1 = MyHGetState(strings);
  3093.             state2 = MyHGetState(subjectArray);
  3094.             MyHLock(strings);
  3095.             MyHLock(subjectArray);
  3096.             for (i = 0, pSubjectArray = *subjectArray; i < numSubjects; i++, pSubjectArray++) {
  3097.                 if (pSubjectArray->inList) {
  3098.                     subject = *strings + pSubjectArray->subjectOffset;
  3099.                     len = strlen(subject);
  3100.                     if (len > 80) len = 80;
  3101.                     subjectWidth = TextWidth(subject, 0, len);
  3102.                     if (subjectWidth > maxSubjectWidth) maxSubjectWidth = subjectWidth;
  3103.                 }
  3104.             }
  3105.             MyHSetState(strings, state1);
  3106.             MyHSetState(subjectArray, state2);
  3107.             (**info).maxSubjectWidth = maxSubjectWidth;
  3108.         }
  3109.         width = maxSubjectWidth + (**info).subjectHCoord + 24;
  3110.         if (width < kMinWindowWidth) width = kMinWindowWidth;
  3111.         
  3112.         longHeight = (long)numCells * (long)lineHeight + panelHeight + 15;
  3113.         if (longHeight > 0x7fff) {
  3114.             height = 0x7fff;
  3115.         } else {
  3116.             height = longHeight;
  3117.             minHeight = MinHeight(wind);
  3118.             if (height < minHeight) height = minHeight;
  3119.         }
  3120.  
  3121.         CalculateZoomRect(wind, width, height, &zoomRect, gPrefs.dontCoverFinderIcons);
  3122.         height = zoomRect.bottom - zoomRect.top;
  3123.         FixHeight(wind, &height);
  3124.         zoomRect.bottom = zoomRect.top + height;
  3125.         (**wState).stdState = zoomRect;
  3126.         if (WindRectEqualRect(wind, &zoomRect)) return noErr;
  3127.     }
  3128.     
  3129.     EraseRect(&wind->portRect);
  3130.     ZoomWindow(wind, zoomDir, false);
  3131.     ResizeContents(wind);
  3132.     return noErr;
  3133. }
  3134.  
  3135.  
  3136.  
  3137. /*----------------------------------------------------------------------------
  3138.     Command 
  3139.     
  3140.     Handle a command for a subject window.
  3141.             
  3142.     Entry:    wind = pointer to subject window.
  3143.             menu = the menu.
  3144.             item = the item.
  3145.             modifiers = modifiers field from event record.
  3146.     
  3147.     Exit:    function result = error code.
  3148. ----------------------------------------------------------------------------*/
  3149.  
  3150. static OSErr Command (WindowPtr wind, short menu, short item, short modifiers)
  3151. {
  3152.     OSErr err = noErr;
  3153.  
  3154.     switch (menu) {
  3155.             
  3156.         case kFileMenu:
  3157.  
  3158.             switch (item) {
  3159.                 case kSaveItem:
  3160.                     err = DoSave(wind, modifiers);
  3161.                     break;
  3162.                 case kSaveAsItem:
  3163.                     err = DoSaveAs(wind);
  3164.                     break;
  3165.                 case kAppendItem:
  3166.                     err = DoAppend(wind);
  3167.                     break;
  3168.                 case kPrintItem:
  3169.                     err = DoPrint(wind);
  3170.                     break;
  3171.             }
  3172.             break;
  3173.             
  3174.         case kEditMenu:
  3175.  
  3176.             switch (item) {
  3177.                 case kSelectAllItem:
  3178.                     DoSelectAll(wind);
  3179.                     break;
  3180.                 case kDeselectAllItem:
  3181.                     DoDeselectAll(wind);
  3182.                     break;
  3183.                 case kFindItem:
  3184.                     err = DoFind(wind);
  3185.                     break;
  3186.                 case kFindAgainItem:
  3187.                     err = DoFindAgain(wind);
  3188.                     break;
  3189.             }
  3190.             break;
  3191.  
  3192.         case kNewsMenu:
  3193.         
  3194.             switch (item) {
  3195.                 case kNextArticleItem:
  3196.                     err = DoNextArticle(wind);
  3197.                     break;
  3198.                 case kNextThreadItem:
  3199.                     err = DoNextThread(wind);
  3200.                     break;
  3201.                 case kNextGroupItem:
  3202.                     err = DoNextGroup(wind);
  3203.                     break;
  3204.                 case kMarkReadItem:
  3205.                     err = DoMarkCommand(wind, true);
  3206.                     break;
  3207.                 case kMarkUnreadItem:
  3208.                     err = DoMarkCommand(wind, false);
  3209.                     break;
  3210.                 case kMarkOthersReadItem:
  3211.                     err = DoMarkOthersRead(wind);
  3212.                     break;
  3213.                 case kReplyItem:
  3214.                     err = DoReply(wind, modifiers);
  3215.                     break;
  3216.                 case kForwardItem:
  3217.                     err = DoForward(wind, modifiers);
  3218.                     break;
  3219.                 case kRedirectItem:
  3220.                     err = DoRedirect(wind, modifiers);
  3221.                     break;
  3222.             }
  3223.             break;
  3224.             
  3225.         case kSpecialMenu:
  3226.         
  3227.             switch (item) {
  3228.                 case kExtractBinariesItem:
  3229.                     err = DoExtractBinaries(wind, modifiers);
  3230.                     break;
  3231.                 case kExtractBinariesManuallyItem:
  3232.                     err = DoExtractBinariesManually(wind, modifiers);
  3233.                     break;
  3234.                 case kSearchItem:
  3235.                     err = DoSearch(wind);
  3236.                     break;
  3237.                 case kCancelArticleItem:
  3238.                     err = DoCancelArticle(wind);
  3239.                     break;
  3240.             }
  3241.      }
  3242.      
  3243.      return err;
  3244. }
  3245.  
  3246.  
  3247.  
  3248. /*----------------------------------------------------------------------------
  3249.     Close 
  3250.     
  3251.     Close a subject window.
  3252.             
  3253.     Entry:    wind = pointer to subject window.
  3254.     
  3255.     Exit:    function result = error code.
  3256. ----------------------------------------------------------------------------*/
  3257.  
  3258. static OSErr Close (WindowPtr wind)
  3259. {
  3260.     TWindow **info;
  3261.     OSErr err = noErr;
  3262.  
  3263.     info = (TWindow**)GetWRefCon(wind);
  3264.     
  3265.     while ((**info).childList != nil) {
  3266.         err = DoClose((**(**info).childList).childWindow);
  3267.         if (err != noErr) return err;
  3268.     }
  3269.     RemoveChild((**info).parentWindow, wind);
  3270.        
  3271.     err = UpdateUnreadList(wind);
  3272.     if (err != noErr) return err;
  3273.     
  3274.     if ((**info).theList != nil) LDispose((**info).theList);
  3275.     MyDisposeHandle((**info).subjectArray);
  3276.     MyDisposeHandle((**info).sortByNumber);
  3277.     MyDisposeHandle((**info).strings);
  3278.     if ((**info).collapseTriangle != nil) KillPoly((**info).collapseTriangle);
  3279.     if ((**info).expandTriangle != nil) KillPoly((**info).expandTriangle);
  3280.     MyDisposeHandle(info);
  3281.  
  3282.     MyDisposeWindow(wind);
  3283.     return noErr;
  3284. }
  3285.  
  3286.  
  3287.  
  3288. /*----------------------------------------------------------------------------
  3289.     Idle 
  3290.     
  3291.     Handle idle time tasks for a subject window.
  3292.             
  3293.     Entry:    wind = pointer to subject window.
  3294.     
  3295.     Exit:    cursorRgn = cursor region for WaitNextEvent mouse moved events.
  3296. ----------------------------------------------------------------------------*/
  3297.  
  3298. static void Idle (WindowPtr wind, RgnHandle cursorRgn)
  3299. {
  3300.     TWindow **info;
  3301.     ListHandle theList;
  3302.     unsigned long fileEnabled, editEnabled, newsEnabled, specialEnabled;
  3303.  
  3304.     info = (TWindow**)GetWRefCon(wind);
  3305.     theList = (**info).theList;
  3306.     SetCursor(&qd.arrow);
  3307.     fileEnabled = kSubjectFileEnabled;
  3308.     editEnabled = kSubjectEditEnabled;
  3309.     newsEnabled = kSubjectNewsEnabled;
  3310.     specialEnabled = kSubjectSpecialEnabled;
  3311.     if ((**theList).dataBounds.bottom == 0)
  3312.         editEnabled &= ~kSelectAllMask;
  3313.     if (!ListHasSelectedCell(theList)) {
  3314.         editEnabled &= ~kDeselectAllMask;
  3315.         fileEnabled &= ~(kSaveMask | kSaveAsMask | kAppendMask | kPrintMask);
  3316.         newsEnabled &= ~(kMarkReadMask | kMarkUnreadMask | kMarkOthersReadMask |
  3317.             kReplyMask | kForwardMask | kRedirectMask);
  3318.         specialEnabled &= ~(kExtractBinariesMask | 
  3319.             kExtractBinariesManuallyMask | kCancelArticleMask);
  3320.     }
  3321.     if (*gFindPattern == 0) editEnabled &= ~kFindAgainMask;
  3322.     SetMenusTo(kAppleAllEnabled, fileEnabled, editEnabled, 
  3323.         newsEnabled, specialEnabled, kSubjectWindEnabled);
  3324. }
  3325.  
  3326.  
  3327.  
  3328. /*----------------------------------------------------------------------------
  3329.     Help 
  3330.     
  3331.     Handle help balloons for a subject window.
  3332.             
  3333.     Entry:    wind = pointer to subject window.
  3334.             where = current mouse location in local coordinates.
  3335. ----------------------------------------------------------------------------*/
  3336.  
  3337. static void Help (WindowPtr wind, Point where)
  3338. {
  3339.     TWindow **info;
  3340.     Rect r;
  3341.     Point tip = {0,0};
  3342.     short subjectAuthorHCoord, x, y;
  3343.     short panelHeight, visTop, cellHeight, cellDataLen, index;
  3344.     TSubject theSubject;
  3345.     TSubject **subjectArray;
  3346.     FontInfo fontInfo;
  3347.     Cell theCell;
  3348.     ListHandle theList;
  3349.  
  3350.     if (DoSizeBoxAndVerticalScrollBarBalloons(wind, where)) return;
  3351.     info = (TWindow**)GetWRefCon(wind);
  3352.     panelHeight = (**info).panelHeight;
  3353.     theList = (**info).theList;
  3354.     visTop = (**theList).visible.top;
  3355.     cellHeight = (**theList).cellSize.v;
  3356.     subjectArray = (**info).subjectArray;
  3357.     GetFontInfo(&fontInfo);
  3358.     SetRect(&r, 0, wind->portRect.bottom - 14, 15, wind->portRect.bottom);
  3359.     if (PtInRect(where, &r)) {
  3360.         ShowHelpBalloon(tip, &r, (**info).windPosLocked ? 23 : 22);
  3361.         return;
  3362.     }
  3363.     subjectAuthorHCoord = (**info).authorsShown ? 
  3364.         (**info).authorHCoord : (**info).subjectHCoord;
  3365.     SetRect(&r, subjectAuthorHCoord, 
  3366.         panelHeight, 
  3367.         wind->portRect.right - 15, 
  3368.         wind->portRect.bottom - 15);
  3369.     if (PtInRect(where, &r)) {
  3370.         ShowHelpBalloon(where, &r, (**info).authorsShown ? 14 : 15);
  3371.         return;
  3372.     }
  3373.     x = ((**info).threadCountHCoord + (**info).checkHCoord) >> 1;
  3374.     r.left = x;
  3375.     r.right = subjectAuthorHCoord;
  3376.     if (PtInRect(where, &r)) {
  3377.         SetPt(&tip, (r.left + r.right) >> 1, where.v);
  3378.         ShowHelpBalloon(tip, &r, 21);
  3379.         return;
  3380.     }
  3381.     y = (**theList).indent.h + fontInfo.ascent;
  3382.     r.left = y;
  3383.     r.right = x;
  3384.     if (PtInRect(where, &r)) {
  3385.         SetPt(&tip, (r.left + r.right) >> 1, where.v);
  3386.         ShowHelpBalloon(tip, &r, 20);
  3387.         return;
  3388.     }
  3389.     SetRect(&r, 0, 0, y, 0);
  3390.     if (where.h > r.right) return;
  3391.     if (!PtInListCell(where, theList)) return;
  3392.     theCell.h = 0;
  3393.     theCell.v = (where.v - panelHeight)/cellHeight + visTop;
  3394.     cellDataLen = 2;
  3395.     LGetCell(&index, &cellDataLen, theCell, theList);
  3396.     theSubject = (*subjectArray)[index];
  3397.     r.top = (theCell.v - visTop) * cellHeight + panelHeight;
  3398.     r.bottom = r.top + cellHeight;
  3399.     if (theSubject.threadLength == 1) {
  3400.         ShowHelpBalloon(tip, &r, 18);
  3401.     } else if (theSubject.threadOrdinal > 1) {
  3402.         ShowHelpBalloon(tip, &r, 19);
  3403.     } else if (theSubject.collapsed) {
  3404.         ShowHelpBalloon(tip, &r, 16);
  3405.     } else {
  3406.         ShowHelpBalloon(tip, &r, 17);
  3407.     }
  3408. }
  3409.  
  3410.  
  3411.  
  3412. /*----------------------------------------------------------------------------
  3413.     InitSubjectDispatchTable 
  3414.     
  3415.     Initialize the dispatch table for subject windows.
  3416. ----------------------------------------------------------------------------*/
  3417.  
  3418. void InitSubjectDispatchTable (void)
  3419. {
  3420.     TDispatch *d;
  3421.     
  3422.     d = &gDispatch[kSubject];
  3423.     
  3424.     d->activate = Activate;
  3425.     d->update = Update;
  3426.     d->mouse = Mouse;
  3427.     d->draggable = Draggable;
  3428.     d->key = Key;
  3429.     d->grow = Grow;
  3430.     d->zoom = Zoom;
  3431.     d->command = Command;
  3432.     d->close = Close;
  3433.     d->idle = Idle;
  3434.     d->help = Help;
  3435.  
  3436.     gExtractBinariesManuallyClickLoopUPP = 
  3437.         NewListClickLoopProc(ExtractBinariesManuallyClickLoop);
  3438.     gOldListClickLoopUPP =
  3439.         NewListClickLoopProc(OldListClickLoop);
  3440.     gSubjectListClickLoopUPP =
  3441.         NewListClickLoopProc(SubjectListClickLoop);
  3442.     gExtractBinariesManuallyHandleTrackingUPP =
  3443.         NewDragTrackingHandlerProc(ExtractBinariesManuallyHandleTracking);
  3444.     gExtractBinariesManuallyHandleReceiveUPP =
  3445.         NewDragReceiveHandlerProc(ExtractBinariesManuallyHandleReceive);
  3446.     gDragSubjectsSendProcUPP = 
  3447.         NewDragSendDataProc(DragSubjectsSendProc);
  3448.     gExtractBinariesManuallyDialogFilterUPP =
  3449.         NewModalFilterProc(ExtractBinariesManuallyDialogFilter);
  3450.     gExtractBinariesManuallyDlgLabelUserItemUPP =
  3451.         NewUserItemProc(ExtractBinariesManuallyDlgLabelUserItem);
  3452.     gExtractBinariesManuallyDlgListUserItemUPP =
  3453.         NewUserItemProc(ExtractBinariesManuallyDlgListUserItem);
  3454. }
  3455.